diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 501e54bf15..c8b1e57f32 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -979,6 +979,7 @@ public protocol SharedAccountContext: AnyObject { func makeStarsGiftController(context: AccountContext, birthdays: [EnginePeer.Id: TelegramBirthday]?, completion: @escaping (([EnginePeer.Id]) -> Void)) -> ViewController func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController + func makeGiftOptionsController(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption]) -> ViewController func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index b8ec6c8e80..9109fdecc6 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -136,6 +136,7 @@ public struct Namespaces { public static let cachedRevenueStats: Int8 = 39 public static let recommendedApps: Int8 = 40 public static let starsReactionDefaultToPrivate: Int8 = 41 + public static let cachedPremiumGiftCodeOptions: Int8 = 42 } public struct UnorderedItemList { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift index 65bff012cc..6e82c5dd51 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift @@ -148,6 +148,79 @@ func _internal_getPremiumGiveawayInfo(account: Account, peerId: EnginePeer.Id, m } } +public final class CachedPremiumGiftCodeOptions: Codable { + public let options: [PremiumGiftCodeOption] + + public init(options: [PremiumGiftCodeOption]) { + self.options = options + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.options = try container.decode([PremiumGiftCodeOption].self, forKey: "t") + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode(self.options, forKey: "t") + } +} + +func _internal_premiumGiftCodeOptions(account: Account, peerId: EnginePeer.Id?, onlyCached: Bool = false) -> Signal<[PremiumGiftCodeOption], NoError> { + let cached = account.postbox.transaction { transaction -> Signal<[PremiumGiftCodeOption], NoError> in + if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPremiumGiftCodeOptions, key: ValueBoxKey(length: 0)))?.get(CachedPremiumGiftCodeOptions.self) { + return .single(entry.options) + } + return .single([]) + } |> switchToLatest + + var flags: Int32 = 0 + if let _ = peerId { + flags |= 1 << 0 + } + let remote = account.postbox.transaction { transaction -> Peer? in + if let peerId = peerId { + return transaction.getPeer(peerId) + } + return nil + } + |> mapToSignal { peer in + let inputPeer = peer.flatMap(apiInputPeer) + return account.network.request(Api.functions.payments.getPremiumGiftCodeOptions(flags: flags, boostPeer: inputPeer)) + |> map(Optional.init) + |> `catch` { _ -> Signal<[Api.PremiumGiftCodeOption]?, NoError> in + return .single(nil) + } + |> mapToSignal { results -> Signal<[PremiumGiftCodeOption], NoError> in + let options = results?.map { PremiumGiftCodeOption(apiGiftCodeOption: $0) } ?? [] + return account.postbox.transaction { transaction -> [PremiumGiftCodeOption] in + if peerId == nil { + if let entry = CodableEntry(CachedPremiumGiftCodeOptions(options: options)) { + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPremiumGiftCodeOptions, key: ValueBoxKey(length: 0)), entry: entry) + } + } + return options + } + } + } + if peerId == nil { + return cached + |> mapToSignal { cached in + if onlyCached && !cached.isEmpty { + return .single(cached) + } else { + return .single(cached) + |> then(remote) + } + } + } else { + return remote + } +} + + func _internal_premiumGiftCodeOptions(account: Account, peerId: EnginePeer.Id?) -> Signal<[PremiumGiftCodeOption], NoError> { var flags: Int32 = 0 if let _ = peerId { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index b383dd994a..5a80d83372 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -54,8 +54,8 @@ public extension TelegramEngine { return _internal_applyPremiumGiftCode(account: self.account, slug: slug) } - public func premiumGiftCodeOptions(peerId: EnginePeer.Id?) -> Signal<[PremiumGiftCodeOption], NoError> { - return _internal_premiumGiftCodeOptions(account: self.account, peerId: peerId) + public func premiumGiftCodeOptions(peerId: EnginePeer.Id?, onlyCached: Bool = false) -> Signal<[PremiumGiftCodeOption], NoError> { + return _internal_premiumGiftCodeOptions(account: self.account, peerId: peerId, onlyCached: onlyCached) } public func premiumGiveawayInfo(peerId: EnginePeer.Id, messageId: EngineMessage.Id) -> Signal { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 82d0c3e30e..59ac95207b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -5683,7 +5683,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI @objc private func nameButtonPressed() { if let item = self.item, let peer = item.message.author { let messageReference = MessageReference(item.message) - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if peer.id.isVerificationCodes, let forwardAuthor = item.content.firstMessage.forwardInfo?.author { + if let channel = forwardAuthor as? TelegramChannel, case .broadcast = channel.info { + item.controllerInteraction.openPeer(EnginePeer(channel), .chat(textInputState: nil, subject: nil, peekData: nil), messageReference, .default) + } else { + item.controllerInteraction.openPeer(EnginePeer(forwardAuthor), .info(nil), messageReference, .default) + } + } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), messageReference, .default) } else { item.controllerInteraction.openPeer(EnginePeer(peer), .info(nil), messageReference, .groupParticipant(storyStats: nil, avatarHeaderNode: nil)) diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index d358dd40ea..e73031098a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -438,6 +438,8 @@ final class GiftSetupScreenComponent: Component { contentHeight += environment.navigationHeight contentHeight += 26.0 + + let giftConfiguration = GiftConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) var introSectionItems: [AnyComponentWithIdentity] = [] introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag)))) @@ -451,10 +453,10 @@ final class GiftSetupScreenComponent: Component { return ListMultilineTextFieldItemComponent.ResetText(value: $0) }, placeholder: "Enter Message", - autocapitalizationType: .none, - autocorrectionType: .no, + autocapitalizationType: .sentences, + autocorrectionType: .yes, returnKeyType: .done, - characterLimit: 255, + characterLimit: Int(giftConfiguration.maxCaptionLength), displayCharacterLimit: true, emptyLineHandling: .notAllowed, formatMenuAvailability: .available([.bold, .italic, .underline, .strikethrough, .spoiler]), @@ -1062,3 +1064,27 @@ public final class GiftSetupScreen: ViewControllerComponentContainer { super.containerLayoutUpdated(layout, transition: transition) } } + +private struct GiftConfiguration { + static var defaultValue: GiftConfiguration { + return GiftConfiguration(maxCaptionLength: 255) + } + + let maxCaptionLength: Int32 + + fileprivate init(maxCaptionLength: Int32) { + self.maxCaptionLength = maxCaptionLength + } + + static func with(appConfiguration: AppConfiguration) -> GiftConfiguration { + if let data = appConfiguration.data { + var maxCaptionLength: Int32? + if let value = data["stargifts_message_length_max"] as? Double { + maxCaptionLength = Int32(value) + } + return GiftConfiguration(maxCaptionLength: maxCaptionLength ?? GiftConfiguration.defaultValue.maxCaptionLength) + } else { + return .defaultValue + } + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index d7c14b4bc6..885086b89a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -135,7 +135,6 @@ private final class GiftViewSheetContent: CombinedComponent { return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value - let controller = environment.controller let component = context.component let theme = environment.theme @@ -294,17 +293,12 @@ private final class GiftViewSheetContent: CombinedComponent { ) ), action: { -// if "".isEmpty { -// component.openPeer(peer) -// Queue.mainQueue().after(1.0, { -// component.cancel(false) -// }) -// } else { - if let controller = controller() as? GiftViewScreen, let navigationController = controller.navigationController, let chatController = navigationController.viewControllers.first(where: { $0 is ChatController }) as? ChatController { - chatController.playShakeAnimation() - } - component.cancel(true) -// } + if "".isEmpty { + component.openPeer(peer) + Queue.mainQueue().after(1.0, { + component.cancel(false) + }) + } } ) ) @@ -798,12 +792,17 @@ public class GiftViewScreen: ViewControllerComponentContainer { self.dismissAnimated() + let title: String = added ? "Gift Saved to Profile" : "Gift Removed from Profile" + var text = added ? "The gift is now displayed in [your profile]()." : "The gift is no longer displayed in [your profile]()." + if let _ = updateSavedToProfile { + text = text.replacingOccurrences(of: "]()", with: "").replacingOccurrences(of: "[", with: "") + } if let navigationController { Queue.mainQueue().after(0.5) { if let lastController = navigationController.viewControllers.last as? ViewController { let resultController = UndoOverlayController( presentationData: presentationData, - content: .sticker(context: context, file: arguments.gift.file, loop: false, title: added ? "Gift Saved to Profile" : "Gift Removed from Profile", text: added ? "The gift is now displayed in [your profile]()." : "The gift is no longer displayed in [your profile]().", undoText: nil, customAction: nil), + content: .sticker(context: context, file: arguments.gift.file, loop: false, title: title, text: text, undoText: nil, customAction: nil), elevatedLayout: lastController is ChatController, action: { [weak navigationController] action in if case .info = action, let navigationController { @@ -854,11 +853,11 @@ public class GiftViewScreen: ViewControllerComponentContainer { self?.dismissAnimated() if let navigationController { - if let starsContext = context.starsContext { - navigationController.pushViewController(context.sharedContext.makeStarsTransactionsScreen(context: context, starsContext: starsContext), animated: true) - } - Queue.mainQueue().after(0.5) { + if let starsContext = context.starsContext { + navigationController.pushViewController(context.sharedContext.makeStarsTransactionsScreen(context: context, starsContext: starsContext), animated: true) + } + if let lastController = navigationController.viewControllers.last as? ViewController { let resultController = UndoOverlayController( presentationData: presentationData, diff --git a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift index fda244d1c2..7623336e54 100644 --- a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift +++ b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift @@ -413,7 +413,7 @@ public final class ListMultilineTextFieldItemComponent: Component { environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) ) - let textLimitLabelFrame = CGRect(origin: CGPoint(x: availableSize.width - textLimitLabelSize.width - rightInset, y: verticalInset + 2.0), size: textLimitLabelSize) + let textLimitLabelFrame = CGRect(origin: CGPoint(x: availableSize.width - textLimitLabelSize.width - leftInset + 5.0, y: verticalInset + 2.0), size: textLimitLabelSize) if let textLimitLabelView = textLimitLabel.view { if textLimitLabelView.superview == nil { textLimitLabelView.isUserInteractionEnabled = false diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index a1b50340e2..692c16fd51 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -384,6 +384,7 @@ final class PeerInfoScreenData { let starsRevenueStatsContext: StarsRevenueStatsContext? let revenueStatsState: RevenueStats? let profileGiftsContext: ProfileGiftsContext? + let premiumGiftOptions: [PremiumGiftCodeOption] let _isContact: Bool var forceIsContact: Bool = false @@ -430,7 +431,8 @@ final class PeerInfoScreenData { starsRevenueStatsState: StarsRevenueStats?, starsRevenueStatsContext: StarsRevenueStatsContext?, revenueStatsState: RevenueStats?, - profileGiftsContext: ProfileGiftsContext? + profileGiftsContext: ProfileGiftsContext?, + premiumGiftOptions: [PremiumGiftCodeOption] ) { self.peer = peer self.chatPeer = chatPeer @@ -466,6 +468,7 @@ final class PeerInfoScreenData { self.starsRevenueStatsContext = starsRevenueStatsContext self.revenueStatsState = revenueStatsState self.profileGiftsContext = profileGiftsContext + self.premiumGiftOptions = premiumGiftOptions } } @@ -959,7 +962,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, starsRevenueStatsState: nil, starsRevenueStatsContext: nil, revenueStatsState: nil, - profileGiftsContext: nil + profileGiftsContext: nil, + premiumGiftOptions: [] ) } } @@ -1005,7 +1009,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen starsRevenueStatsState: nil, starsRevenueStatsContext: nil, revenueStatsState: nil, - profileGiftsContext: nil + profileGiftsContext: nil, + premiumGiftOptions: [] )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -1017,11 +1022,17 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen groupsInCommon = nil } + let premiumGiftOptions: Signal<[PremiumGiftCodeOption], NoError> let profileGiftsContext: ProfileGiftsContext? if case .user = kind { profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: userPeerId) + premiumGiftOptions = .single([]) + |> then( + context.engine.payments.premiumGiftCodeOptions(peerId: nil, onlyCached: true) + ) } else { profileGiftsContext = nil + premiumGiftOptions = .single([]) } enum StatusInputData: Equatable { @@ -1275,9 +1286,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasBotPreviewItems, peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false), privacySettings, - starsRevenueContextAndState + starsRevenueContextAndState, + premiumGiftOptions ) - |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, premiumGiftOptions -> PeerInfoScreenData in var availablePanes = availablePanes if isMyProfile { availablePanes?.insert(.stories, at: 0) @@ -1394,7 +1406,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen starsRevenueStatsState: starsRevenueContextAndState.1, starsRevenueStatsContext: starsRevenueContextAndState.0, revenueStatsState: nil, - profileGiftsContext: profileGiftsContext + profileGiftsContext: profileGiftsContext, + premiumGiftOptions: premiumGiftOptions ) } case .channel: @@ -1604,7 +1617,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen starsRevenueStatsState: starsRevenueContextAndState.1, starsRevenueStatsContext: starsRevenueContextAndState.0, revenueStatsState: revenueContextAndState.1, - profileGiftsContext: nil + profileGiftsContext: nil, + premiumGiftOptions: [] ) } case let .group(groupId): @@ -1905,7 +1919,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen starsRevenueStatsState: nil, starsRevenueStatsContext: nil, revenueStatsState: nil, - profileGiftsContext: nil + profileGiftsContext: nil, + premiumGiftOptions: [] )) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 8d4948be80..029669f500 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -9816,35 +9816,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private func openPremiumGift() { - guard let cachedData = self.data?.cachedData as? CachedUserData else { + guard let premiumGiftOptions = self.data?.premiumGiftOptions else { return } - var pushControllerImpl: ((ViewController) -> Void)? - let controller = PremiumGiftScreen(context: self.context, peerIds: [self.peerId], options: cachedData.premiumGiftOptions, source: .profile, pushController: { c in - pushControllerImpl?(c) - }, completion: { [weak self] in - if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is PeerInfoScreen) && !($0 is PremiumGiftScreen) } - var foundController = false - for controller in controllers.reversed() { - if let chatController = controller as? ChatController, case .peer(id: strongSelf.peerId) = chatController.chatLocation { - chatController.hintPlayNextOutgoingGift() - foundController = true - break - } - } - if !foundController { - let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: strongSelf.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) - chatController.hintPlayNextOutgoingGift() - controllers.append(chatController) - } - navigationController.setViewControllers(controllers, animated: true) - } - }) - pushControllerImpl = { [weak controller] c in - controller?.push(c) - } + let premiumOptions = premiumGiftOptions.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } + + let controller = self.context.sharedContext.makeGiftOptionsController( + context: self.context, + peerId: self.peerId, + premiumOptions: premiumOptions + ) self.controller?.push(controller) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index bbdf81e5d1..f3f2e820e4 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2405,6 +2405,15 @@ public final class SharedAccountContextImpl: SharedAccountContext { return controller } + public func makeGiftOptionsController(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption]) -> ViewController { + guard let starsContext = context.starsContext else { + fatalError() + } + let controller = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peerId, premiumOptions: premiumOptions) + controller.navigationPresentation = .modal + return controller + } + public func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController { let mappedSubject: PremiumPrivacyScreen.Subject let introSource: PremiumIntroSource