diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b73ecd50be..2dcb26b0ca 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12246,6 +12246,7 @@ Sorry for the inconvenience."; "Stars.Intro.Transaction.FragmentTopUp.Title" = "Stars Top-Up"; "Stars.Intro.Transaction.FragmentTopUp.Subtitle" = "via Fragment"; "Stars.Intro.Transaction.Unsupported.Title" = "Unsupported"; +"Stars.Intro.Transaction.Refund" = "Refund"; "Stars.Intro.PurchasedTitle" = "Stars Acquired"; "Stars.Intro.PurchasedText" = "**%@** added to your balance."; @@ -12269,6 +12270,7 @@ Sorry for the inconvenience."; "Stars.Transaction.Via" = "Via"; "Stars.Transaction.To" = "To"; +"Stars.Transaction.From" = "From"; "Stars.Transaction.Id" = "Transaction ID"; "Stars.Transaction.Date" = "Date"; "Stars.Transaction.Terms" = "Review the [Terms of Service]() for Stars."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index b318177283..c272a9d0ed 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1043,7 +1043,7 @@ public protocol SharedAccountContext: AnyObject { func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [StarsTopUpOption], peerId: EnginePeer.Id?, requiredStars: Int64?, completion: @escaping (Int64) -> Void) -> ViewController - func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>) -> ViewController + func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController func makeStarsTransactionScreen(context: AccountContext, transaction: StarsContext.State.Transaction) -> ViewController func makeStarsReceiptScreen(context: AccountContext, receipt: BotPaymentReceipt) -> ViewController diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift index 87153683ba..6e7883caa7 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift @@ -333,7 +333,7 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg let cleanQuery = cleanHashtag(query) if !cleanQuery.isEmpty { - self.currentController?.beginMessageSearch(cleanQuery) + self.currentController?.beginMessageSearch("#" + cleanQuery) self.myChatContents?.hashtagSearchUpdate(query: cleanQuery) self.myController?.beginMessageSearch("#" + cleanQuery) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index cc8a2cd84a..868a392f63 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -199,7 +199,7 @@ private final class StarsContextImpl { return } var transactions = state.transactions - transactions.insert(.init(id: "tmp_\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil), at: 0) + transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil), at: 0) self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: state.balance + balance, transactions: transactions, canLoadMore: state.canLoadMore, isLoading: state.isLoading)) } @@ -250,7 +250,7 @@ private extension StarsContext.State.Transaction { } parsedPeer = .peer(EnginePeer(peer)) } - self.init(id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init)) + self.init(flags: [], id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init)) } } } @@ -258,6 +258,17 @@ private extension StarsContext.State.Transaction { public final class StarsContext { public struct State: Equatable { public struct Transaction: Equatable { + public struct Flags: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let isRefund = Flags(rawValue: 1 << 0) + public static let isLocal = Flags(rawValue: 1 << 1) + } + public enum Peer: Equatable { case appStore case playMarket @@ -267,6 +278,7 @@ public final class StarsContext { case peer(EnginePeer) } + public let flags: Flags public let id: String public let count: Int64 public let date: Int32 @@ -276,6 +288,7 @@ public final class StarsContext { public let photo: TelegramMediaWebFile? public init( + flags: Flags, id: String, count: Int64, date: Int32, @@ -284,6 +297,7 @@ public final class StarsContext { description: String?, photo: TelegramMediaWebFile? ) { + self.flags = flags self.id = id self.count = count self.date = date @@ -449,13 +463,13 @@ private final class StarsTransactionsContextImpl { if filteredTransactions != initialTransactions { var existingIds = Set() for transaction in self._state.transactions { - if !transaction.id.hasPrefix("tmp_") { + if !transaction.flags.contains(.isLocal) { existingIds.insert(transaction.id) } } var updatedState = self._state - updatedState.transactions.removeAll(where: { $0.id.hasPrefix("tmp_") }) + updatedState.transactions.removeAll(where: { $0.flags.contains(.isLocal) }) for transaction in filteredTransactions.reversed() { if !existingIds.contains(transaction.id) { updatedState.transactions.insert(transaction, at: 0) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift index 31d6bf2960..3ba5605471 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift @@ -297,7 +297,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { if let toPeer { tableItems.append(.init( id: "to", - title: strings.Stars_Transaction_To, + title: count < 0 ? strings.Stars_Transaction_To : strings.Stars_Transaction_From, component: AnyComponent( Button( content: AnyComponent( @@ -1097,6 +1097,9 @@ private final class TransactionCellComponent: Component { ) func brokenLine(_ string: String) -> String { + if string.count > 30 { + return string + } let middleIndex = string.index(string.startIndex, offsetBy: string.count / 2) var newString = string newString.insert("\n", at: middleIndex) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift index e96d78080e..4f2f37720d 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -16,6 +16,16 @@ import AvatarNode import BundleIconComponent import PhotoResources +private extension StarsContext.State.Transaction { + var extendedId: String { + if self.count > 0 { + return "\(id)_in" + } else { + return "\(id)_out" + } + } +} + final class StarsTransactionsListPanelComponent: Component { typealias EnvironmentType = StarsTransactionsPanelEnvironment @@ -161,7 +171,7 @@ final class StarsTransactionsListPanelComponent: Component { continue } let item = self.items[index] - let id = item.id + let id = item.extendedId validIds.insert(id) var itemTransition = transition @@ -186,7 +196,7 @@ final class StarsTransactionsListPanelComponent: Component { let itemTitle: String let itemSubtitle: String? - let itemDate: String + var itemDate: String switch item.peer { case let .peer(peer): if let title = item.title { @@ -225,6 +235,9 @@ final class StarsTransactionsListPanelComponent: Component { itemLabel = NSAttributedString(string: labelString, font: Font.medium(fontBaseDisplaySize), textColor: labelString.hasPrefix("-") ? environment.theme.list.itemDestructiveColor : environment.theme.list.itemDisclosureActions.constructive.fillColor) itemDate = stringForMediumCompactDate(timestamp: item.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat) + if item.flags.contains(.isRefund) { + itemDate += " – \(environment.strings.Stars_Intro_Transaction_Refund)" + } var titleComponents: [AnyComponentWithIdentity] = [] titleComponents.append( @@ -272,7 +285,7 @@ final class StarsTransactionsListPanelComponent: Component { guard let self, let component = self.component else { return } - if !item.id.hasPrefix("tmp_") { + if !item.flags.contains(.isLocal) { component.action(item) } } @@ -320,7 +333,7 @@ final class StarsTransactionsListPanelComponent: Component { let bottomOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height) let loadMore = bottomOffset < 100.0 if environment.isCurrent, loadMore { - let lastId = self.items.last?.id + let lastId = self.items.last?.extendedId if lastId != self.currentLoadMoreId || lastId == nil { self.currentLoadMoreId = lastId component.transactionsContext.loadMore() @@ -344,14 +357,14 @@ final class StarsTransactionsListPanelComponent: Component { return } let wasEmpty = self.items.isEmpty - let hadTemporaryTransactions = self.items.contains(where: { $0.id.hasPrefix("tmp_") }) + let hadLocalTransactions = self.items.contains(where: { $0.flags.contains(.isLocal) }) self.items = status.transactions if !status.isLoading { self.currentLoadMoreId = nil } if !self.isUpdating { - state?.updated(transition: wasEmpty || hadTemporaryTransactions ? .immediate : .easeInOut(duration: 0.2)) + state?.updated(transition: wasEmpty || hadLocalTransactions ? .immediate : .easeInOut(duration: 0.2)) } }) } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index 7a3e586442..0011c8dcf7 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -427,6 +427,7 @@ private final class SheetContent: CombinedComponent { action: { _ in return true}) controller?.present(resultController, in: .window(.root)) + controller?.complete(paid: true) controller?.dismissAnimated() starsContext.load(force: true) @@ -549,15 +550,18 @@ private final class StarsTransferSheetComponent: CombinedComponent { public final class StarsTransferScreen: ViewControllerComponentContainer { private let context: AccountContext + private let completion: @escaping () -> Void public init( context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, - inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>, + completion: @escaping (Bool) -> Void ) { self.context = context + self.completion = completion super.init( context: context, @@ -578,10 +582,23 @@ public final class StarsTransferScreen: ViewControllerComponentContainer { starsContext.load(force: false) } + deinit { + self.complete(paid: false) + } + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + private var didComplete = false + fileprivate func complete(paid: Bool) { + guard !self.didComplete else { + return + } + self.didComplete = true + self.completion(paid) + } + public func dismissAnimated() { if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { view.dismissAnimated() diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 61e03c9347..5eb3cce511 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2957,7 +2957,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(messageId), inputData: starsInputData) + let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(messageId), inputData: starsInputData, completion: { _ in }) strongSelf.push(controller) }) } else { diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 4116410c7b..3395f95322 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -837,7 +837,7 @@ func openResolvedUrlImpl( } } let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { _ in - let controller = context.sharedContext.makeStarsTransferScreen(context: context, starsContext: starsContext, invoice: invoice, source: .slug(slug), inputData: starsInputData) + let controller = context.sharedContext.makeStarsTransferScreen(context: context, starsContext: starsContext, invoice: invoice, source: .slug(slug), inputData: starsInputData, completion: { _ in }) navigationController.pushViewController(controller) }) } else { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 3a50c5ae7b..e6c02fc58a 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2627,8 +2627,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return StarsPurchaseScreen(context: context, starsContext: starsContext, options: options, peerId: peerId, requiredStars: requiredStars, modal: true, completion: completion) } - public func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>) -> ViewController { - return StarsTransferScreen(context: context, starsContext: starsContext, invoice: invoice, source: source, inputData: inputData) + public func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController { + return StarsTransferScreen(context: context, starsContext: starsContext, invoice: invoice, source: source, inputData: inputData, completion: completion) } public func makeStarsTransactionScreen(context: AccountContext, transaction: StarsContext.State.Transaction) -> ViewController { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index fae7d484b7..14416985c8 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -877,7 +877,19 @@ public final class WebAppController: ViewController, AttachmentContainable { } } let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { _ in - let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .slug(slug), inputData: starsInputData) + let controller = strongSelf.context.sharedContext.makeStarsTransferScreen( + context: strongSelf.context, + starsContext: starsContext, + invoice: invoice, + source: .slug(slug), + inputData: starsInputData, + completion: { [weak self] paid in + guard let self else { + return + } + self?.sendInvoiceClosedEvent(slug: slug, result: paid ? .paid : .cancelled) + } + ) navigationController.pushViewController(controller) }) } else {