diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 40163038bd..2ee614d0c8 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14466,7 +14466,9 @@ Sorry for the inconvenience."; "Premium.Todo" = "Checklists"; "Premium.TodoInfo" = "Plan, assign and complete tasks – seamlessly and efficiently."; -"CreateTodo.Title" = "Title"; +"CreateTodo.Title" = "New Checklist"; +"CreateTodo.AddTitle" = "Add a Task"; +"CreateTodo.EditTitle" = "Edit Checklist"; "CreateTodo.TodoTitle" = "CHECKLIST"; "CreateTodo.TitlePlaceholder" = "Title"; "CreateTodo.TaskPlaceholder" = "Task"; @@ -14474,6 +14476,8 @@ Sorry for the inconvenience."; "CreateTodo.TaskCountFooterFormat_1" = "You can add {count} more task."; "CreateTodo.TaskCountFooterFormat_any" = "You can add {count} more tasks."; "CreateTodo.TaskCountLimitReached" = "Maximum number of tasks reached."; +"CreateTodo.Save" = "Save"; +"CreateTodo.Send" = "Send"; "CreateTodo.AllowOthersToComplete" = "Allow Others to Mark as Done"; "CreateTodo.AllowOthersToAppend" = "Allow Others to Add Tasks"; @@ -14499,3 +14503,5 @@ Sorry for the inconvenience."; "Bot.AddToGroup.Title" = "Add to Group"; "Bot.AddToChannel.Title" = "Add to Channel"; + +"ScheduledMessages.TodoUnavailable" = "Voting will become available after the message is published."; diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index cfd6d9d0a5..b6efad2c57 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -234,7 +234,7 @@ final class ComposePollScreenComponent: Component { private func item(at point: CGPoint) -> (AnyHashable, ComponentView)? { let localPoint = self.pollOptionsSectionContainer.convert(point, from: self) for (id, itemView) in self.pollOptionsSectionContainer.itemViews { - if let view = itemView.contents.view as? ListComposePollOptionComponent.View, !view.isRevealed { + if let view = itemView.contents.view as? ListComposePollOptionComponent.View, !view.isRevealed && !view.currentText.isEmpty { let viewFrame = view.convert(view.bounds, to: self.pollOptionsSectionContainer) let iconFrame = CGRect(origin: CGPoint(x: viewFrame.maxX - viewFrame.height, y: viewFrame.minY), size: CGSize(width: viewFrame.height, height: viewFrame.height)) if iconFrame.contains(localPoint) { diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 8590765693..466f63e0c1 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -204,6 +204,13 @@ private func collectExternalShareItems(strings: PresentationStrings, dateTimeFor } } signals.append(.single(.done(.text(text)))) + } else if let mediaReference = item.mediaReference, let todo = mediaReference.media as? TelegramMediaTodo { + var text = "☑️ \(todo.text)" + for item in todo.items { + let completed = todo.completions.contains(where: { $0.id == item.id }) + text.append("\n\(completed ? "+" : "-") \(item.text)") + } + signals.append(.single(.done(.text(text)))) } else if let mediaReference = item.mediaReference, let contact = mediaReference.media as? TelegramMediaContact { let contactData: DeviceContactExtendedData if let vCard = contact.vCardData, let vCardData = vCard.data(using: .utf8), let parsed = DeviceContactExtendedData(vcard: vCardData) { diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 4f24755979..3ec7c3169e 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1351,11 +1351,11 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributes[1] = boldAttributes let resultString: PresentationStrings.FormattedString - if completed.count > 1 { + if completed.count > 1 || (completed.count == 1 && taskTitle == nil) { resultString = strings.Notification_TodoMultipleCompleted(peerName, strings.Notification_TodoTasks(Int32(completed.count))) } else if let _ = completed.first { resultString = strings.Notification_TodoCompleted(peerName, taskTitle ?? "") - } else if incompleted.count > 1 { + } else if incompleted.count > 1 || (incompleted.count == 1 && taskTitle == nil) { resultString = strings.Notification_TodoMultipleIncompleted(peerName, strings.Notification_TodoTasks(Int32(incompleted.count))) } else if let _ = incompleted.first { resultString = strings.Notification_TodoIncompleted(peerName, taskTitle ?? "") diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift index 91c1259efa..8ca3596d8b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift @@ -569,39 +569,19 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { } } -// fileprivate var absoluteRect: (CGRect, CGSize)? -// fileprivate func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { -// self.absoluteRect = (rect, containerSize) -// guard let backgroundWallpaperNode = self.backgroundWallpaperNode else { -// return -// } -// guard !self.sourceNode.isExtractedToContextPreview else { -// return -// } -// let mappedRect = CGRect(origin: CGPoint(x: rect.minX + backgroundWallpaperNode.frame.minX, y: rect.minY + backgroundWallpaperNode.frame.minY), size: rect.size) -// backgroundWallpaperNode.update(rect: mappedRect, within: containerSize) -// } -// -// fileprivate func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { -// guard let backgroundWallpaperNode = self.backgroundWallpaperNode else { -// return -// } -// backgroundWallpaperNode.offset(value: value, animationCurve: animationCurve, duration: duration) -// } -// -// fileprivate func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { -// guard let backgroundWallpaperNode = self.backgroundWallpaperNode else { -// return -// } -// backgroundWallpaperNode.offsetSpring(value: value, duration: duration, damping: damping) -// } - @objc private func buttonPressed() { guard !self.ignoreNextTap else { self.ignoreNextTap = false return } - if let radioNode = self.radioNode, let isChecked = radioNode.isChecked, self.canMark, self.isPremium { + + var isScheduledMessages = false + if let message = self.message, Namespaces.Message.allScheduled.contains(message.id.namespace) { + isScheduledMessages = true + } + let canUpdate = self.canMark && self.isPremium && !isScheduledMessages + + if let radioNode = self.radioNode, let isChecked = radioNode.isChecked, canUpdate { radioNode.updateIsChecked(!isChecked, animated: true) self.selectionUpdated?() } else { @@ -616,7 +596,10 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { return { context, presentationData, presentationContext, message, todo, option, completion, translation, constrainedWidth in var canMark = false if (todo.flags.contains(.othersCanComplete) || message.author?.id == context.account.peerId) { - canMark = true + if let forwardInfo = message.forwardInfo, let authorId = forwardInfo.author?.id, authorId != context.account.peerId { + } else { + canMark = true + } } let leftInset: CGFloat = canMark ? 57.0 : 29.0 diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift index 9f467571e0..5fdd9d9118 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift @@ -57,7 +57,7 @@ final class ComposeTodoScreenComponent: Component { let id: Int32 let textInputState = TextFieldComponent.ExternalState() let textFieldTag = NSObject() - var resetText: String? + var resetText: NSAttributedString? init(id: Int32) { self.id = id @@ -87,7 +87,7 @@ final class ComposeTodoScreenComponent: Component { private let todoTextInputState = TextFieldComponent.ExternalState() private let todoTextFieldTag = NSObject() - private var resetTodoText: String? + private var resetTodoText: NSAttributedString? private var nextTodoItemId: Int32 = 1 private var todoItems: [TodoItem] = [] @@ -182,7 +182,7 @@ final class ComposeTodoScreenComponent: Component { private func item(at point: CGPoint) -> (AnyHashable, ComponentView)? { let localPoint = self.todoItemsSectionContainer.convert(point, from: self) for (id, itemView) in self.todoItemsSectionContainer.itemViews { - if let view = itemView.contents.view as? ListComposePollOptionComponent.View, !view.isRevealed { + if let view = itemView.contents.view as? ListComposePollOptionComponent.View, !view.isRevealed && !view.currentText.isEmpty { let viewFrame = view.convert(view.bounds, to: self.todoItemsSectionContainer) let iconFrame = CGRect(origin: CGPoint(x: viewFrame.maxX - viewFrame.height, y: viewFrame.minY), size: CGSize(width: viewFrame.height, height: viewFrame.height)) if iconFrame.contains(localPoint) { @@ -591,13 +591,13 @@ final class ComposeTodoScreenComponent: Component { let isFirstTime = self.component == nil if self.component == nil { if let existingTodo = component.initialData.existingTodo { - self.resetTodoText = existingTodo.text + self.resetTodoText = chatInputStateStringWithAppliedEntities(existingTodo.text, entities: existingTodo.textEntities) for item in existingTodo.items { let todoItem = ComposeTodoScreenComponent.TodoItem( id: item.id ) - todoItem.resetText = item.text + todoItem.resetText = chatInputStateStringWithAppliedEntities(item.text, entities: item.entities) self.todoItems.append(todoItem) } self.nextTodoItemId = (existingTodo.items.max(by: { $0.id < $1.id })?.id ?? 0) + 1 @@ -777,7 +777,7 @@ final class ComposeTodoScreenComponent: Component { strings: environment.strings, isEnabled: canEdit, resetText: self.resetTodoText.flatMap { resetText in - return ListComposePollOptionComponent.ResetText(value: NSAttributedString(string: resetText)) + return ListComposePollOptionComponent.ResetText(value: resetText) }, assumeIsEditing: self.inputMediaNodeTargetTag === self.todoTextFieldTag, characterLimit: component.initialData.maxTodoTextLength, @@ -860,7 +860,7 @@ final class ComposeTodoScreenComponent: Component { strings: environment.strings, isEnabled: isEnabled, resetText: todoItem.resetText.flatMap { resetText in - return ListComposePollOptionComponent.ResetText(value: NSAttributedString(string: resetText)) + return ListComposePollOptionComponent.ResetText(value: resetText) }, assumeIsEditing: self.inputMediaNodeTargetTag === todoItem.textFieldTag, characterLimit: component.initialData.maxTodoItemLength, @@ -931,12 +931,12 @@ final class ComposeTodoScreenComponent: Component { var i = 0 for line in lines { if i < self.todoItems.count { - self.todoItems[i].resetText = line + self.todoItems[i].resetText = NSAttributedString(string: line) } else { let todoItem = ComposeTodoScreenComponent.TodoItem( id: self.nextTodoItemId ) - todoItem.resetText = line + todoItem.resetText = NSAttributedString(string: line) self.todoItems.append(todoItem) self.nextTodoItemId += 1 } @@ -1641,14 +1641,14 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont let presentationData = context.sharedContext.currentPresentationData.with { $0 } if !initialData.canEdit && initialData.existingTodo != nil { - self.title = "Add a Task" + self.title = presentationData.strings.CreateTodo_Title } else { - self.title = initialData.existingTodo != nil ? "Edit To Do List" : "New To Do List" + self.title = initialData.existingTodo != nil ? presentationData.strings.CreateTodo_EditTitle : presentationData.strings.CreateTodo_Title } self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false) - let sendButtonItem = UIBarButtonItem(title: initialData.existingTodo != nil ? "Save" : presentationData.strings.CreatePoll_Create, style: .done, target: self, action: #selector(self.sendPressed)) + let sendButtonItem = UIBarButtonItem(title: initialData.existingTodo != nil ? presentationData.strings.CreateTodo_Save : presentationData.strings.CreateTodo_Send, style: .done, target: self, action: #selector(self.sendPressed)) self.sendButtonItem = sendButtonItem self.navigationItem.setRightBarButton(sendButtonItem, animated: false) sendButtonItem.isEnabled = false diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/TonTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/TonTransactionsScreen.swift deleted file mode 100644 index d556a49edf..0000000000 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/TonTransactionsScreen.swift +++ /dev/null @@ -1,1055 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import ComponentFlow -import SwiftSignalKit -import ViewControllerComponent -import ComponentDisplayAdapters -import TelegramPresentationData -import AccountContext -import TelegramCore -import Postbox -import MultilineTextComponent -import BalancedTextComponent -import Markdown -import PremiumStarComponent -import ListSectionComponent -import BundleIconComponent -import TextFormat -import UndoUI -import ListActionItemComponent -import StarsAvatarComponent -import TelegramStringFormatting -import ListItemComponentAdaptor -import ItemListUI -import StarsWithdrawalScreen -import PremiumDiamondComponent - -final class TonTransactionsScreenComponent: Component { - typealias EnvironmentType = ViewControllerComponentContainer.Environment - - let context: AccountContext - let tonContext: StarsContext - let openTransaction: (StarsContext.State.Transaction) -> Void - let buy: () -> Void - let withdraw: () -> Void - let showTimeoutTooltip: (Int32) -> Void - let gift: () -> Void - - init( - context: AccountContext, - starsContext: StarsContext, - openTransaction: @escaping (StarsContext.State.Transaction) -> Void, - buy: @escaping () -> Void, - withdraw: @escaping () -> Void, - showTimeoutTooltip: @escaping (Int32) -> Void, - gift: @escaping () -> Void - ) { - self.context = context - self.tonContext = starsContext - self.openTransaction = openTransaction - self.buy = buy - self.withdraw = withdraw - self.showTimeoutTooltip = showTimeoutTooltip - self.gift = gift - } - - static func ==(lhs: TonTransactionsScreenComponent, rhs: TonTransactionsScreenComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.tonContext !== rhs.tonContext { - return false - } - return true - } - - private final class ScrollViewImpl: UIScrollView { - override func touchesShouldCancel(in view: UIView) -> Bool { - return true - } - - override var contentOffset: CGPoint { - set(value) { - var value = value - if value.y > self.contentSize.height - self.bounds.height { - value.y = max(0.0, self.contentSize.height - self.bounds.height) - self.bounces = false - } else { - self.bounces = true - } - super.contentOffset = value - } get { - return super.contentOffset - } - } - } - - class View: UIView, UIScrollViewDelegate { - private let scrollView: ScrollViewImpl - - private var currentSelectedPanelId: AnyHashable? - - private let navigationBackgroundView: BlurredBackgroundView - private let navigationSeparatorLayer: SimpleLayer - private let navigationSeparatorLayerContainer: SimpleLayer - - private let scrollContainerView: UIView - - private let overscroll = ComponentView() - private let fade = ComponentView() - private let starView = ComponentView() - private let titleView = ComponentView() - private let descriptionView = ComponentView() - - private let balanceView = ComponentView() - private let earnStarsSection = ComponentView() - - private let topBalanceTitleView = ComponentView() - private let topBalanceValueView = ComponentView() - private let topBalanceIconView = ComponentView() - - private let panelContainer = ComponentView() - - private var component: TonTransactionsScreenComponent? - private weak var state: EmptyComponentState? - private var navigationMetrics: (navigationHeight: CGFloat, statusBarHeight: CGFloat)? - private var controller: (() -> ViewController?)? - - private var enableVelocityTracking: Bool = false - private var previousVelocityM1: CGFloat = 0.0 - private var previousVelocity: CGFloat = 0.0 - - private var listIsExpanded = false - - private var ignoreScrolling: Bool = false - - private var stateDisposable: Disposable? - private var starsState: StarsContext.State? - - private var previousBalance: StarsAmount? - - private var allTransactionsContext: StarsTransactionsContext? - private var incomingTransactionsContext: StarsTransactionsContext? - private var outgoingTransactionsContext: StarsTransactionsContext? - - override init(frame: CGRect) { - self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - self.navigationBackgroundView.alpha = 0.0 - - self.navigationSeparatorLayer = SimpleLayer() - self.navigationSeparatorLayer.opacity = 0.0 - self.navigationSeparatorLayerContainer = SimpleLayer() - self.navigationSeparatorLayerContainer.opacity = 0.0 - - self.scrollContainerView = UIView() - self.scrollView = ScrollViewImpl() - - super.init(frame: frame) - - self.scrollView.delaysContentTouches = true - self.scrollView.canCancelContentTouches = true - self.scrollView.clipsToBounds = false - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.scrollView.contentInsetAdjustmentBehavior = .never - } - if #available(iOS 13.0, *) { - self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false - } - self.scrollView.showsVerticalScrollIndicator = false - self.scrollView.showsHorizontalScrollIndicator = false - self.scrollView.alwaysBounceHorizontal = false - self.scrollView.scrollsToTop = false - self.scrollView.delegate = self - self.scrollView.clipsToBounds = true - self.addSubview(self.scrollView) - - self.scrollView.addSubview(self.scrollContainerView) - - self.addSubview(self.navigationBackgroundView) - - self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer) - self.layer.addSublayer(self.navigationSeparatorLayerContainer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.stateDisposable?.dispose() - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - guard let result = super.hitTest(point, with: event) else { - return nil - } - var currentParent: UIView? = result - while true { - if currentParent == nil || currentParent === self { - break - } - if let scrollView = currentParent as? UIScrollView { - if scrollView === self.scrollView { - break - } - if scrollView.isDecelerating && scrollView.contentOffset.y < -scrollView.contentInset.top { - return self.scrollView - } - } - currentParent = currentParent?.superview - } - return result - } - - func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - self.enableVelocityTracking = true - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - guard !self.ignoreScrolling else { - return - } - if self.enableVelocityTracking { - self.previousVelocityM1 = self.previousVelocity - if let value = (scrollView.value(forKey: (["_", "verticalVelocity"] as [String]).joined()) as? NSNumber)?.doubleValue { - self.previousVelocity = CGFloat(value) - } - } - - self.updateScrolling(transition: .immediate) - } - - func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - guard let navigationMetrics = self.navigationMetrics else { - return - } - - if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { - let paneAreaExpansionFinalPoint: CGFloat = panelContainerView.frame.minY - navigationMetrics.navigationHeight - if abs(scrollView.contentOffset.y - paneAreaExpansionFinalPoint) < .ulpOfOne { - panelContainerView.transferVelocity(self.previousVelocityM1) - } - } - } - - func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - guard let _ = self.navigationMetrics else { - return - } - - let paneAreaExpansionDistance: CGFloat = 32.0 - let paneAreaExpansionFinalPoint: CGFloat = scrollView.contentSize.height - scrollView.bounds.height - if targetContentOffset.pointee.y > paneAreaExpansionFinalPoint - paneAreaExpansionDistance && targetContentOffset.pointee.y < paneAreaExpansionFinalPoint { - targetContentOffset.pointee.y = paneAreaExpansionFinalPoint - self.enableVelocityTracking = false - self.previousVelocity = 0.0 - self.previousVelocityM1 = 0.0 - } - } - - func scrollToTop() { - if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View, !panelContainerView.scrollToTop() { - self.scrollView.setContentOffset(.zero, animated: true) - } - } - - private func updateScrolling(transition: ComponentTransition) { - let scrollBounds = self.scrollView.bounds - - let isLockedAtPanels = scrollBounds.maxY == self.scrollView.contentSize.height - - if let navigationMetrics = self.navigationMetrics { - let topInset: CGFloat = navigationMetrics.navigationHeight - 56.0 - - let titleOffset: CGFloat - let titleScale: CGFloat - let titleOffsetDelta = (topInset + 160.0) - (navigationMetrics.statusBarHeight + (navigationMetrics.navigationHeight - navigationMetrics.statusBarHeight) / 2.0) - - var topContentOffset = self.scrollView.contentOffset.y - - let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0 - topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0 - titleOffset = topContentOffset - let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta)) - titleScale = 1.0 - fraction * 0.36 - - let headerTransition: ComponentTransition = .immediate - - if let starView = self.starView.view { - let starPosition = CGPoint(x: self.scrollView.frame.width / 2.0, y: topInset + starView.bounds.height / 2.0 - 30.0 - titleOffset * titleScale) - headerTransition.setPosition(view: starView, position: starPosition) - headerTransition.setScale(view: starView, scale: titleScale) - } - - if let titleView = self.titleView.view { - let titlePosition = CGPoint(x: scrollBounds.width / 2.0, y: max(topInset + 160.0 - titleOffset, navigationMetrics.statusBarHeight + (navigationMetrics.navigationHeight - navigationMetrics.statusBarHeight) / 2.0)) - - headerTransition.setPosition(view: titleView, position: titlePosition) - headerTransition.setScale(view: titleView, scale: titleScale) - } - - let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut)) - animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) - animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) - - let expansionDistance: CGFloat = 32.0 - var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance - expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) - - transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) - if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { - panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) - } - - let topBalanceAlpha = 1.0 - expansionDistanceFactor - if let view = self.topBalanceTitleView.view { - view.alpha = topBalanceAlpha - } - if let view = self.topBalanceValueView.view { - view.alpha = topBalanceAlpha - } - if let view = self.topBalanceIconView.view { - view.alpha = topBalanceAlpha - } - - let listIsExpanded = expansionDistanceFactor == 0.0 - if listIsExpanded != self.listIsExpanded { - self.listIsExpanded = listIsExpanded - if !self.isUpdating { - self.state?.updated(transition: .init(animation: .curve(duration: 0.25, curve: .slide))) - } - } - } - - let _ = self.panelContainer.updateEnvironment( - transition: transition, - environment: { - StarsTransactionsPanelContainerEnvironment(isScrollable: isLockedAtPanels) - } - ) - } - - private var isUpdating = false - func update(component: TonTransactionsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.isUpdating = true - defer { - self.isUpdating = false - } - - self.component = component - self.state = state - - var balanceUpdated = false - if let starsState = self.starsState { - if let previousBalance = self.previousBalance, starsState.balance != previousBalance { - balanceUpdated = true - } - self.previousBalance = starsState.balance - } - - let environment = environment[ViewControllerComponentContainer.Environment.self].value - - if self.stateDisposable == nil { - self.stateDisposable = (component.tonContext.state - |> deliverOnMainQueue).start(next: { [weak self] state in - guard let self else { - return - } - self.starsState = state - - if !self.isUpdating { - self.state?.updated() - } - }) - } - - var wasLockedAtPanels = false - if let panelContainerView = self.panelContainer.view, let navigationMetrics = self.navigationMetrics { - if self.scrollView.bounds.minY > 0.0 && abs(self.scrollView.bounds.minY - (panelContainerView.frame.minY - navigationMetrics.navigationHeight)) <= UIScreenPixel { - wasLockedAtPanels = true - } - } - - self.controller = environment.controller - - self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight) - - self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - - let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)) - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame) - - let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)) - - transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame) - transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size)) - - self.backgroundColor = environment.theme.list.blocksBackgroundColor - - var contentHeight: CGFloat = 0.0 - - let sideInsets: CGFloat = environment.safeInsets.left + environment.safeInsets.right + 16.0 * 2.0 - let bottomInset: CGFloat = environment.safeInsets.bottom - - if environment.statusBarHeight > 0.0 { - contentHeight += environment.statusBarHeight - } else { - contentHeight += 12.0 - } - - let starTransition: ComponentTransition = .immediate - - var topBackgroundColor = environment.theme.list.plainBackgroundColor - let bottomBackgroundColor = environment.theme.list.blocksBackgroundColor - if environment.theme.overallDarkAppearance { - topBackgroundColor = bottomBackgroundColor - } - - let overscrollSize = self.overscroll.update( - transition: .immediate, - component: AnyComponent(Rectangle(color: topBackgroundColor)), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 1000.0) - ) - let overscrollFrame = CGRect(origin: CGPoint(x: 0.0, y: -overscrollSize.height), size: overscrollSize) - if let overscrollView = self.overscroll.view { - if overscrollView.superview == nil { - self.scrollView.addSubview(overscrollView) - } - starTransition.setFrame(view: overscrollView, frame: overscrollFrame) - } - - let fadeSize = self.fade.update( - transition: .immediate, - component: AnyComponent(RoundedRectangle( - colors: [ - topBackgroundColor, - bottomBackgroundColor - ], - cornerRadius: 0.0, - gradientDirection: .vertical - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 1000.0) - ) - let fadeFrame = CGRect(origin: CGPoint(x: 0.0, y: -fadeSize.height), size: fadeSize) - if let fadeView = self.fade.view { - if fadeView.superview == nil { - self.scrollView.addSubview(fadeView) - } - starTransition.setFrame(view: fadeView, frame: fadeFrame) - } - - let starSize = self.starView.update( - transition: .immediate, - component: AnyComponent(PremiumDiamondComponent()), - environment: {}, - containerSize: CGSize(width: min(414.0, availableSize.width), height: 220.0) - ) - let starFrame = CGRect(origin: .zero, size: starSize) - if let starView = self.starView.view { - if starView.superview == nil { - self.insertSubview(starView, aboveSubview: self.scrollView) - } - starTransition.setBounds(view: starView, bounds: starFrame) - } - - //TODO:localize - let titleSize = self.titleView.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString(string: "TON", font: Font.bold(28.0), textColor: environment.theme.list.itemPrimaryTextColor)), - horizontalAlignment: .center, - truncationType: .end, - maximumNumberOfLines: 1 - ) - ), - environment: {}, - containerSize: availableSize - ) - if let titleView = self.titleView.view { - if titleView.superview == nil { - self.addSubview(titleView) - } - starTransition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleSize)) - } - - let topBalanceTitleSize = self.topBalanceTitleView.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Stars_Intro_Balance, - font: Font.regular(14.0), - textColor: environment.theme.actionSheet.primaryTextColor - )), - maximumNumberOfLines: 1 - )), - environment: {}, - containerSize: CGSize(width: 120.0, height: 100.0) - ) - - let formattedBalance = formatStarsAmountText(self.starsState?.balance ?? StarsAmount.zero, dateTimeFormat: environment.dateTimeFormat) - let smallLabelFont = Font.regular(11.0) - let labelFont = Font.semibold(14.0) - let balanceText = tonAmountAttributedString(formattedBalance, integralFont: labelFont, fractionalFont: smallLabelFont, color: environment.theme.actionSheet.primaryTextColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator) - - let topBalanceValueSize = self.topBalanceValueView.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(balanceText), - maximumNumberOfLines: 1 - )), - environment: {}, - containerSize: CGSize(width: 120.0, height: 100.0) - ) - let topBalanceIconSize = self.topBalanceIconView.update( - transition: .immediate, - component: AnyComponent(BundleIconComponent(name: "Ads/TonAbout", tintColor: nil)), - environment: {}, - containerSize: availableSize - ) - - let navigationHeight = environment.navigationHeight - environment.statusBarHeight - let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - topBalanceTitleSize.height - topBalanceValueSize.height) / 2.0 - let topBalanceTitleFrame = CGRect(origin: CGPoint(x: availableSize.width - topBalanceTitleSize.width - 16.0 - environment.safeInsets.right, y: topBalanceOriginY), size: topBalanceTitleSize) - if let topBalanceTitleView = self.topBalanceTitleView.view { - if topBalanceTitleView.superview == nil { - topBalanceTitleView.alpha = 0.0 - self.addSubview(topBalanceTitleView) - } - starTransition.setFrame(view: topBalanceTitleView, frame: topBalanceTitleFrame) - } - - let topBalanceValueFrame = CGRect(origin: CGPoint(x: availableSize.width - topBalanceValueSize.width - 16.0 - environment.safeInsets.right, y: topBalanceTitleFrame.maxY), size: topBalanceValueSize) - if let topBalanceValueView = self.topBalanceValueView.view { - if topBalanceValueView.superview == nil { - topBalanceValueView.alpha = 0.0 - self.addSubview(topBalanceValueView) - } - starTransition.setFrame(view: topBalanceValueView, frame: topBalanceValueFrame) - } - - let topBalanceIconFrame = CGRect(origin: CGPoint(x: topBalanceValueFrame.minX - topBalanceIconSize.width - 2.0, y: floorToScreenPixels(topBalanceValueFrame.midY - topBalanceIconSize.height / 2.0) - UIScreenPixel), size: topBalanceIconSize) - if let topBalanceIconView = self.topBalanceIconView.view { - if topBalanceIconView.superview == nil { - topBalanceIconView.alpha = 0.0 - self.addSubview(topBalanceIconView) - } - starTransition.setFrame(view: topBalanceIconView, frame: topBalanceIconFrame) - } - - contentHeight += 181.0 - - //TODO:localize - let descriptionSize = self.descriptionView.update( - transition: .immediate, - component: AnyComponent( - BalancedTextComponent( - text: .plain(NSAttributedString(string: "Offer TON to submit post suggestions to channels on Telegram.", font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.2 - ) - ), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInsets - 8.0, height: 240.0) - ) - let descriptionFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - descriptionSize.width) / 2.0), y: contentHeight + 20.0 - floor(descriptionSize.height / 2.0)), size: descriptionSize) - if let descriptionView = self.descriptionView.view { - if descriptionView.superview == nil { - self.scrollView.addSubview(descriptionView) - } - - starTransition.setFrame(view: descriptionView, frame: descriptionFrame) - } - - contentHeight += descriptionSize.height - contentHeight += 29.0 - - let withdrawAvailable = "".isEmpty //(self.revenueState?.balances.overallRevenue.value ?? 0) > 0 - - let balanceSize = self.balanceView.update( - transition: .immediate, - component: AnyComponent(ListSectionComponent( - theme: environment.theme, - header: nil, - footer: nil, - items: [AnyComponentWithIdentity(id: 0, component: AnyComponent( - StarsBalanceComponent( - theme: environment.theme, - strings: environment.strings, - dateTimeFormat: environment.dateTimeFormat, - count: self.starsState?.balance ?? StarsAmount.zero, - currency: .ton, - rate: 2.99 * 1e-9, - actionTitle: "Withdraw via Fragment", - actionAvailable: withdrawAvailable, - actionIsEnabled: true, - actionIcon: nil, - action: { [weak self] in - guard let self, let component = self.component else { - return - } - component.withdraw() - }, - secondaryActionTitle: nil, - secondaryActionIcon: nil, - secondaryAction: nil, - additionalAction: nil - ) - ))] - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInsets, height: availableSize.height) - ) - let balanceFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - balanceSize.width) / 2.0), y: contentHeight), size: balanceSize) - if let balanceView = self.balanceView.view { - if balanceView.superview == nil { - self.scrollView.addSubview(balanceView) - } - starTransition.setFrame(view: balanceView, frame: balanceFrame) - } - contentHeight += balanceSize.height - contentHeight += 34.0 - - let initialTransactions = self.starsState?.transactions ?? [] - var panelItems: [StarsTransactionsPanelContainerComponent.Item] = [] - if !initialTransactions.isEmpty { - let allTransactionsContext: StarsTransactionsContext - if let current = self.allTransactionsContext { - allTransactionsContext = current - } else { - allTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(subject: .starsContext(component.tonContext), mode: .all) - self.allTransactionsContext = allTransactionsContext - } - - let incomingTransactionsContext: StarsTransactionsContext - if let current = self.incomingTransactionsContext { - incomingTransactionsContext = current - } else { - incomingTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(subject: .starsContext(component.tonContext), mode: .incoming) - self.incomingTransactionsContext = incomingTransactionsContext - } - - let outgoingTransactionsContext: StarsTransactionsContext - if let current = self.outgoingTransactionsContext { - outgoingTransactionsContext = current - } else { - outgoingTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(subject: .starsContext(component.tonContext), mode: .outgoing) - self.outgoingTransactionsContext = outgoingTransactionsContext - } - - panelItems.append(StarsTransactionsPanelContainerComponent.Item( - id: "all", - title: environment.strings.Stars_Intro_AllTransactions, - panel: AnyComponent(StarsTransactionsListPanelComponent( - context: component.context, - transactionsContext: allTransactionsContext, - isAccount: true, - action: { transaction in - component.openTransaction(transaction) - } - )) - )) - - panelItems.append(StarsTransactionsPanelContainerComponent.Item( - id: "incoming", - title: environment.strings.Stars_Intro_Incoming, - panel: AnyComponent(StarsTransactionsListPanelComponent( - context: component.context, - transactionsContext: incomingTransactionsContext, - isAccount: true, - action: { transaction in - component.openTransaction(transaction) - } - )) - )) - - panelItems.append(StarsTransactionsPanelContainerComponent.Item( - id: "outgoing", - title: environment.strings.Stars_Intro_Outgoing, - panel: AnyComponent(StarsTransactionsListPanelComponent( - context: component.context, - transactionsContext: outgoingTransactionsContext, - isAccount: true, - action: { transaction in - component.openTransaction(transaction) - } - )) - )) - } - - var panelTransition = transition - if balanceUpdated { - panelTransition = .easeInOut(duration: 0.25) - } - - if !panelItems.isEmpty { - let panelContainerInset: CGFloat = self.listIsExpanded ? 0.0 : 16.0 - let panelContainerCornerRadius: CGFloat = self.listIsExpanded ? 0.0 : 11.0 - - let panelContainerSize = self.panelContainer.update( - transition: panelTransition, - component: AnyComponent(StarsTransactionsPanelContainerComponent( - theme: environment.theme, - strings: environment.strings, - dateTimeFormat: environment.dateTimeFormat, - insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left + panelContainerInset, bottom: bottomInset, right: environment.safeInsets.right + panelContainerInset), - items: panelItems, - currentPanelUpdated: { [weak self] id, transition in - guard let self else { - return - } - self.currentSelectedPanelId = id - self.state?.updated(transition: transition) - } - )), - environment: { - StarsTransactionsPanelContainerEnvironment(isScrollable: wasLockedAtPanels) - }, - containerSize: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight) - ) - if let panelContainerView = self.panelContainer.view { - if panelContainerView.superview == nil { - self.scrollContainerView.addSubview(panelContainerView) - } - transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - panelContainerSize.width) / 2.0), y: contentHeight), size: panelContainerSize)) - transition.setCornerRadius(layer: panelContainerView.layer, cornerRadius: panelContainerCornerRadius) - } - contentHeight += panelContainerSize.height - } else { - self.panelContainer.view?.removeFromSuperview() - } - - self.ignoreScrolling = true - - let contentOffset = self.scrollView.bounds.minY - transition.setPosition(view: self.scrollView, position: CGRect(origin: CGPoint(), size: availableSize).center) - let contentSize = CGSize(width: availableSize.width, height: contentHeight) - if self.scrollView.contentSize != contentSize { - self.scrollView.contentSize = contentSize - } - transition.setFrame(view: self.scrollContainerView, frame: CGRect(origin: CGPoint(), size: contentSize)) - - var scrollViewBounds = self.scrollView.bounds - scrollViewBounds.size = availableSize - if wasLockedAtPanels, let panelContainerView = self.panelContainer.view { - scrollViewBounds.origin.y = panelContainerView.frame.minY - environment.navigationHeight - } - transition.setBounds(view: self.scrollView, bounds: scrollViewBounds) - - if !wasLockedAtPanels && !transition.animation.isImmediate && self.scrollView.bounds.minY != contentOffset { - let deltaOffset = self.scrollView.bounds.minY - contentOffset - transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: -deltaOffset), to: CGPoint(), additive: true) - } - - self.ignoreScrolling = false - - self.updateScrolling(transition: transition) - - return availableSize - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} - -public final class TonTransactionsScreen: ViewControllerComponentContainer { - private let context: AccountContext - private let tonContext: StarsContext - - private let options = Promise<[StarsTopUpOption]>() - - private let navigateDisposable = MetaDisposable() - - private weak var tooltipScreen: UndoOverlayController? - private var timer: Foundation.Timer? - - public init(context: AccountContext, tonContext: StarsContext, forceDark: Bool = false) { - self.context = context - self.tonContext = tonContext - - var buyImpl: (() -> Void)? - var withdrawImpl: (() -> Void)? - var showTimeoutTooltipImpl: ((Int32) -> Void)? - var giftImpl: (() -> Void)? - var openTransactionImpl: ((StarsContext.State.Transaction) -> Void)? - super.init(context: context, component: TonTransactionsScreenComponent( - context: self.context, - starsContext: self.tonContext, - openTransaction: { transaction in - openTransactionImpl?(transaction) - }, - buy: { - buyImpl?() - }, - withdraw: { - withdrawImpl?() - }, - showTimeoutTooltip: { timestamp in - showTimeoutTooltipImpl?(timestamp) - }, - gift: { - giftImpl?() - } - ), navigationBarAppearance: .transparent) - - self.navigationPresentation = .modalInLargeLayout - - self.options.set(.single([]) |> then(context.engine.payments.starsTopUpOptions())) - - openTransactionImpl = { [weak self] transaction in - guard let self else { - return - } - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer else { - return - } - let controller = context.sharedContext.makeStarsTransactionScreen(context: context, transaction: transaction, peer: peer) - self.push(controller) - }) - } - - buyImpl = { [weak self] in - guard let self else { - return - } - let _ = (self.options.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] options in - guard let self else { - return - } - let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: tonContext, options: options, purpose: .generic, completion: { [weak self] stars in - guard let self else { - return - } - self.tonContext.add(balance: StarsAmount(value: stars, nanos: 0)) - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let resultController = UndoOverlayController( - presentationData: presentationData, - content: .universal( - animation: "StarsBuy", - scale: 0.066, - colors: [:], - title: presentationData.strings.Stars_Intro_PurchasedTitle, - text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(stars))).string, - customUndoText: nil, - timeout: nil - ), - elevatedLayout: false, - action: { _ in return true}) - self.present(resultController, in: .window(.root)) - }) - self.push(controller) - }) - } - - withdrawImpl = { [weak self] in - guard let _ = self else { - return - } - -// let _ = (context.engine.peers.checkStarsRevenueWithdrawalAvailability() -// |> deliverOnMainQueue).start(error: { [weak self] error in -// guard let self else { -// return -// } -// switch error { -// case .serverProvided: -// return -// case .requestPassword: -// let _ = (self.starsRevenueStatsContext.state -// |> take(1) -// |> deliverOnMainQueue).start(next: { [weak self] state in -// guard let self, let stats = state.stats else { -// return -// } -// let controller = self.context.sharedContext.makeStarsWithdrawalScreen(context: context, stats: stats, completion: { [weak self] amount in -// guard let self else { -// return -// } -// let controller = confirmStarsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: amount, present: { [weak self] c, a in -// self?.present(c, in: .window(.root)) -// }, completion: { [weak self] url in -// let presentationData = context.sharedContext.currentPresentationData.with { $0 } -// context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) -// -// Queue.mainQueue().after(2.0) { -// self?.starsRevenueStatsContext.reload() -// } -// }) -// self.present(controller, in: .window(.root)) -// }) -// self.push(controller) -// }) -// default: -// let controller = starsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: 0, initialError: error, present: { [weak self] c, a in -// self?.present(c, in: .window(.root)) -// }, completion: { _ in -// -// }) -// self.present(controller, in: .window(.root)) -// } -// }) - } - - showTimeoutTooltipImpl = { [weak self] cooldownUntilTimestamp in - guard let self, self.tooltipScreen == nil else { - return - } - - let remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let content: UndoOverlayContent = .universal( - animation: "anim_clock", - scale: 0.058, - colors: [:], - title: nil, - text: presentationData.strings.Stars_Withdraw_Withdraw_ErrorTimeout(stringForRemainingTime(remainingCooldownSeconds)).string, - customUndoText: nil, - timeout: nil - ) - let controller = UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in - return true - }) - self.tooltipScreen = controller - self.present(controller, in: .window(.root)) - - if remainingCooldownSeconds < 3600 { - if self.timer == nil { - self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in - guard let self else { - return - } - - if let tooltipScreen = self.tooltipScreen { - let remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) - let content: UndoOverlayContent = .universal( - animation: "anim_clock", - scale: 0.058, - colors: [:], - title: nil, - text: presentationData.strings.Stars_Withdraw_Withdraw_ErrorTimeout(stringForRemainingTime(remainingCooldownSeconds)).string, - customUndoText: nil, - timeout: nil - ) - tooltipScreen.content = content - } else { - if let timer = self.timer { - self.timer = nil - timer.invalidate() - } - } - }) - } - } - } - - giftImpl = { [weak self] in - guard let self else { - return - } - let _ = combineLatest(queue: Queue.mainQueue(), - self.options.get() |> take(1), - self.context.account.stateManager.contactBirthdays |> take(1) - ).start(next: { [weak self] options, birthdays in - guard let self else { - return - } - let controller = self.context.sharedContext.makeStarsGiftController(context: self.context, birthdays: birthdays, completion: { [weak self] peerIds in - guard let self, let peerId = peerIds.first else { - return - } - let purchaseController = self.context.sharedContext.makeStarsPurchaseScreen( - context: self.context, - starsContext: tonContext, - options: options, - purpose: .gift(peerId: peerId), - completion: { [weak self] stars in - guard let self else { - return - } - - if let navigationController = self.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is ContactSelectionController) } - navigationController.setViewControllers(controllers, animated: true) - } - - Queue.mainQueue().after(2.0) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let resultController = UndoOverlayController( - presentationData: presentationData, - content: .universal( - animation: "StarsSend", - scale: 0.066, - colors: [:], - title: nil, - text: presentationData.strings.Stars_Intro_StarsSent(Int32(stars)), - customUndoText: presentationData.strings.Stars_Intro_StarsSent_ViewChat, - timeout: nil - ), - elevatedLayout: false, - action: { [weak self] action in - if case .undo = action, let navigationController = self?.navigationController as? NavigationController { - let _ = (context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) - ) - |> deliverOnMainQueue).start(next: { peer in - guard let peer else { - return - } - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), subject: nil, botStart: nil, updateTextInputState: nil, keepStack: .always, useExisting: true, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: nil, animated: true)) - }) - } - return true - }) - self.present(resultController, in: .window(.root)) - } - } - ) - self.push(purchaseController) - }) - self.push(controller) - }) - } - - self.tonContext.load(force: false) - - self.scrollToTop = { [weak self] in - guard let self else { - return - } - if let componentView = self.node.hostView.componentView as? TonTransactionsScreenComponent.View { - componentView.scrollToTop() - } - } - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.navigateDisposable.dispose() - } - - public func update() { - } -} diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift index 8d4d871b9d..3fb947b592 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift @@ -73,6 +73,24 @@ extension ChatControllerImpl { return } + if !self.context.isPremium { + let controller = UndoOverlayController( + presentationData: self.presentationData, + content: .premiumPaywall(title: nil, text: self.presentationData.strings.Chat_Todo_PremiumRequired, customUndoText: nil, timeout: nil, linkAction: nil), + action: { [weak self] action in + guard let self else { + return false + } + if case .info = action { + let controller = self.context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil) + self.push(controller) + } + return false + } + ) + self.present(controller, in: .current) + } + let _ = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: message.id, completedIds: [], incompletedIds: [todoItemId]).start() }))) } @@ -85,6 +103,24 @@ extension ChatControllerImpl { return } + if !self.context.isPremium { + let controller = UndoOverlayController( + presentationData: self.presentationData, + content: .premiumPaywall(title: nil, text: self.presentationData.strings.Chat_Todo_PremiumRequired, customUndoText: nil, timeout: nil, linkAction: nil), + action: { [weak self] action in + guard let self else { + return false + } + if case .info = action { + let controller = self.context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil) + self.push(controller) + } + return false + } + ) + self.present(controller, in: .current) + } + let _ = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: message.id, completedIds: [todoItemId], incompletedIds: []).start() }))) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index e8e6659451..5183326007 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4925,6 +4925,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } self.dismissAllTooltips() + + guard self.presentationInterfaceState.subject != .scheduledMessages else { + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.ScheduledMessages_TodoUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + return + } if !self.context.isPremium { let controller = UndoOverlayController( presentationData: self.presentationData, diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index cd06f11f36..03277f6fa0 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1573,12 +1573,19 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } if let activeTodo { + var maxTodoItemsCount: Int = 30 + if let data = context.currentAppConfiguration.with({ $0 }).data { + if let value = data["todo_items_max"] as? Double { + maxTodoItemsCount = Int(value) + } + } + var canAppend = false - if message.author?.id == context.account.peerId || activeTodo.flags.contains(.othersCanAppend) { + if activeTodo.items.count < maxTodoItemsCount && (activeTodo.flags.contains(.othersCanAppend) || message.author?.id == context.account.peerId) { canAppend = true } if canAppend { - actions.append(.action(ContextMenuActionItem(text: "Add a Task", icon: { theme in + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Chat_Todo_ContextMenu_AddTask, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in interfaceInteraction.editTodoMessage(messages[0].id, nil, true)