diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 03c5e8be4c..cf541e0c07 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14032,3 +14032,5 @@ Sorry for the inconvenience."; "Privacy.Gifts.PremiumToast.Text" = "Subscribe to **Telegram Premium** to use this setting."; "Privacy.Gifts.PremiumToast.Action" = "Open"; + +"Gift.Send.ErrorDisallowed" = "**%@** doesn't accept this kind of gifts."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 958b1beef7..c8360edda3 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1059,7 +1059,7 @@ public protocol SharedAccountContext: AnyObject { func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController 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 makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Signal)?) -> ViewController func makeGiftOptionsController(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption], hasBirthday: Bool, completion: (() -> Void)?) -> 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/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index 8817f48613..b7f9fc5829 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -177,6 +177,7 @@ public enum BotPaymentFormRequestError { case generic case alreadyActive case noPaymentNeeded + case disallowedStarGift } extension BotPaymentInvoice { @@ -473,6 +474,8 @@ func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, netw |> `catch` { error -> Signal in if error.errorDescription == "NO_PAYMENT_NEEDED" { return .fail(.noPaymentNeeded) + } else if error.errorDescription == "USER_DISALLOWED_STARGIFTS" { + return .fail(.disallowedStarGift) } return .fail(.generic) } @@ -635,6 +638,7 @@ public enum SendBotPaymentFormError { case paymentFailed case alreadyPaid case starGiftOutOfStock + case disallowedStarGift } public enum SendBotPaymentResult { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index f5bbe705a3..b511fefa55 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -766,7 +766,7 @@ func _internal_updateStarGiftsPinnedToTop(account: Account, peerId: EnginePeer.I public enum TransferStarGiftError { case generic - case disallowed + case disallowedStarGift } func _internal_transferStarGift(account: Account, prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) -> Signal { @@ -783,7 +783,10 @@ func _internal_transferStarGift(account: Account, prepaid: Bool, reference: Star } if prepaid { return account.network.request(Api.functions.payments.transferStarGift(stargift: starGift, toId: inputPeer)) - |> mapError { _ -> TransferStarGiftError in + |> mapError { error -> TransferStarGiftError in + if error.errorDescription == "USER_DISALLOWED_STARGIFTS" { + return .disallowedStarGift + } return .generic } |> mapToSignal { updates -> Signal in @@ -798,6 +801,8 @@ func _internal_transferStarGift(account: Account, prepaid: Bool, reference: Star |> `catch` { error -> Signal in if case .noPaymentNeeded = error { return .single(nil) + } else if case .disallowedStarGift = error { + return .fail(.disallowedStarGift) } return .fail(.generic) } @@ -1335,16 +1340,15 @@ private final class ProfileGiftsContextImpl { self.pushState() } - func transferStarGift(prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) { - self.actionDisposable.set( - _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId).startStrict() - ) + func transferStarGift(prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) -> Signal { if let count = self.count { self.count = max(0, count - 1) } self.gifts.removeAll(where: { $0.reference == reference }) self.filteredGifts.removeAll(where: { $0.reference == reference }) self.pushState() + + return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId) } func upgradeStarGift(formId: Int64?, reference: StarGiftReference, keepOriginalInfo: Bool) -> Signal { @@ -1703,9 +1707,17 @@ public final class ProfileGiftsContext { } } - public func transferStarGift(prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) { - self.impl.with { impl in - impl.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId) + public func transferStarGift(prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId).start(error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + })) + } + return disposable } } diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index d54f5269da..5d04a6823e 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -513,6 +513,9 @@ final class GiftOptionsScreenComponent: Component { } let context = component.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + var dismissAlertImpl: (() -> Void)? let alertController = giftTransferAlertController( context: context, gift: transferGift, @@ -521,65 +524,102 @@ final class GiftOptionsScreenComponent: Component { navigationController: mainController.navigationController as? NavigationController, commit: { [weak self, weak mainController] in let proceed: (Bool) -> Void = { waitForTopUp in + var errorImpl: ((TransferStarGiftError) -> Void)? + var completedImpl: (() -> Void)? + if waitForTopUp, let starsContext = context.starsContext { let _ = (starsContext.onUpdate |> deliverOnMainQueue).start(next: { let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id) - |> deliverOnMainQueue).start() + |> deliverOnMainQueue).start(error: { error in + errorImpl?(error) + }, completed: { + completedImpl?() + }) }) } else { let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id) - |> deliverOnMainQueue).start() + |> deliverOnMainQueue).start(error: { error in + errorImpl?(error) + }, completed: { + completedImpl?() + }) } guard let controller = mainController, let navigationController = controller.navigationController as? NavigationController else { return } - if peer.id.namespace == Namespaces.Peer.CloudChannel { - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } - var foundController = false - for controller in controllers.reversed() { - if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId { - foundController = true - break + errorImpl = { [weak navigationController] error in + guard let navigationController else { + return + } + dismissAlertImpl?() + + var errorText: String? + switch error { + case .disallowedStarGift: + errorText = presentationData.strings.Gift_Send_ErrorDisallowed(peer.compactDisplayTitle).string + default: + errorText = presentationData.strings.Gift_Send_ErrorUnknown + } + + if let errorText = errorText { + let alertController = textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true) + if let lastController = navigationController.viewControllers.last as? ViewController { + lastController.present(alertController, in: .window(.root)) } } - if !foundController { - if let controller = context.sharedContext.makePeerInfoController( - context: context, - updatedPresentationData: nil, - peer: peer._asPeer(), - mode: .gifts, - avatarInitiallyExpanded: false, - fromChat: false, - requestsContext: nil - ) { - controllers.append(controller) - } - } - navigationController.setViewControllers(controllers, animated: true) - } else { - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } - var foundController = false - for controller in controllers.reversed() { - if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { - chatController.hintPlayNextOutgoingGift() - foundController = true - break - } - } - if !foundController { - let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) - chatController.hintPlayNextOutgoingGift() - controllers.append(chatController) - } - navigationController.setViewControllers(controllers, animated: true) } - if let completion = component.completion { - completion() + + completedImpl = { + dismissAlertImpl?() + + if peer.id.namespace == Namespaces.Peer.CloudChannel { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } + var foundController = false + for controller in controllers.reversed() { + if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId { + foundController = true + break + } + } + if !foundController { + if let controller = context.sharedContext.makePeerInfoController( + context: context, + updatedPresentationData: nil, + peer: peer._asPeer(), + mode: .gifts, + avatarInitiallyExpanded: false, + fromChat: false, + requestsContext: nil + ) { + controllers.append(controller) + } + } + navigationController.setViewControllers(controllers, animated: true) + } else { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { + chatController.hintPlayNextOutgoingGift() + foundController = true + break + } + } + if !foundController { + let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + chatController.hintPlayNextOutgoingGift() + controllers.append(chatController) + } + navigationController.setViewControllers(controllers, animated: true) + } + if let completion = component.completion { + completion() + } } } @@ -610,6 +650,10 @@ final class GiftOptionsScreenComponent: Component { } ) controller.present(alertController, in: .window(.root)) + + dismissAlertImpl = { [weak alertController] in + alertController?.dismissAnimated() + } } func update(component: GiftOptionsScreenComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index e8239105a8..611fe56346 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -465,12 +465,14 @@ final class GiftSetupScreenComponent: Component { switch error { case .starGiftOutOfStock: errorText = presentationData.strings.Gift_Send_ErrorOutOfStock + case .disallowedStarGift: + errorText = presentationData.strings.Gift_Send_ErrorDisallowed(self.peerMap[peerId]?.compactDisplayTitle ?? "").string default: errorText = presentationData.strings.Gift_Send_ErrorUnknown } if let errorText = errorText { - let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true) controller.present(alertController, in: .window(.root)) } }) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index 66375077f3..f912d2016b 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -50,6 +50,7 @@ swift_library( "//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent", "//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent", "//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController", + "//submodules/ActivityIndicator", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift index 91ccd670ac..a5a063827f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift @@ -13,6 +13,7 @@ import AvatarNode import Markdown import GiftItemComponent import ChatMessagePaymentAlertController +import ActivityIndicator private final class GiftTransferAlertContentNode: AlertContentNode { private let context: AccountContext @@ -31,9 +32,19 @@ private final class GiftTransferAlertContentNode: AlertContentNode { private let actionNodesSeparator: ASDisplayNode private let actionNodes: [TextAlertContentActionNode] private let actionVerticalSeparators: [ASDisplayNode] + + private var activityIndicator: ActivityIndicator? private var validLayout: CGSize? + var inProgress = false { + didSet { + if let size = self.validLayout { + let _ = self.updateLayout(size: size, transition: .immediate) + } + } + } + override var dismissOnOutsideTap: Bool { return self.isUserInteractionEnabled } @@ -248,6 +259,23 @@ private final class GiftTransferAlertContentNode: AlertContentNode { nodeIndex += 1 } + if self.inProgress { + let activityIndicator: ActivityIndicator + if let current = self.activityIndicator { + activityIndicator = current + } else { + activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false)) + self.addSubnode(activityIndicator) + } + + if let actionNode = self.actionNodes.first { + actionNode.isHidden = true + + let indicatorSize = CGSize(width: 22.0, height: 22.0) + transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize)) + } + } + return resultSize } } @@ -274,17 +302,18 @@ public func giftTransferAlertController( buttonText = strings.Gift_Transfer_Confirmation_TransferFree } + var contentNode: GiftTransferAlertContentNode? var dismissImpl: ((Bool) -> Void)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { - dismissImpl?(true) + let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { [weak contentNode] in + contentNode?.inProgress = true commit() }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { dismissImpl?(true) })] - let contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions) + contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions) - let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode, navigationController: navigationController, showBalance: transferStars > 0) + let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: navigationController, showBalance: transferStars > 0) dismissImpl = { [weak controller] animated in if animated { controller?.dismissAnimated() diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 47f6b6d424..d9b86b87b5 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -2415,7 +2415,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { case upgradePreview([StarGift.UniqueGift.Attribute], String) case wearPreview(StarGift.UniqueGift) - var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?, upgradeMessageId: Int32?)? { + var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, pinnedToTop: Bool?, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?, upgradeMessageId: Int32?)? { switch self { case let .message(message): if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { @@ -2427,7 +2427,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { } else { reference = .message(messageId: message.id) } - return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, nil, nil, upgradeMessageId) + return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, canUpgrade, upgradeStars, nil, nil, upgradeMessageId) case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, peerId, senderId, savedId): var reference: StarGiftReference if let peerId, let savedId { @@ -2447,19 +2447,19 @@ public class GiftViewScreen: ViewControllerComponentContainer { } else { incoming = message.flags.contains(.Incoming) } - return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, false, false, false, nil, transferStars, canExportDate, nil) + return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, nil, false, false, false, nil, transferStars, canExportDate, nil) default: return nil } } case let .uniqueGift(gift), let .wearPreview(gift): - return (nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, false, false, false, nil, nil, nil, nil) + return (nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, nil, false, false, false, nil, nil, nil, nil) case let .profileGift(peerId, gift): var messageId: EngineMessage.Id? if case let .message(messageIdValue) = gift.reference { messageId = messageIdValue } - return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate, nil) + return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate, nil) case .soldOutGift: return nil case .upgradePreview: @@ -2488,8 +2488,9 @@ public class GiftViewScreen: ViewControllerComponentContainer { forceDark: Bool = false, updateSavedToProfile: ((StarGiftReference, Bool) -> Void)? = nil, convertToStars: (() -> Void)? = nil, - transferGift: ((Bool, EnginePeer.Id) -> Void)? = nil, + transferGift: ((Bool, EnginePeer.Id) -> Signal)? = nil, upgradeGift: ((Int64?, Bool) -> Signal)? = nil, + togglePinnedToTop: ((Bool) -> Bool)? = nil, shareStory: ((StarGift.UniqueGift) -> Void)? = nil ) { self.context = context @@ -2822,19 +2823,19 @@ public class GiftViewScreen: ViewControllerComponentContainer { } let controller = context.sharedContext.makePremiumGiftController(context: context, source: .starGiftTransfer(birthdays, reference, gift, transferStars, arguments.canExportDate, showSelf), completion: { peerIds in guard let peerId = peerIds.first else { - return + return .complete() } - if let transferGift { - transferGift(transferStars == 0, peerId) - } else { - let _ = (context.engine.payments.transferStarGift(prepaid: transferStars == 0, reference: reference, peerId: peerId) - |> deliverOnMainQueue).start() - } - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(1.5, { if transferStars > 0 { context.starsContext?.load(force: true) } }) + if let transferGift { + return transferGift(transferStars == 0, peerId) + } else { + return (context.engine.payments.transferStarGift(prepaid: transferStars == 0, reference: reference, peerId: peerId) + |> deliverOnMainQueue) + } }) navigationController.pushViewController(controller) }) @@ -2979,6 +2980,37 @@ public class GiftViewScreen: ViewControllerComponentContainer { return } var items: [ContextMenuItem] = [] + let strings = presentationData.strings + + if let _ = arguments.reference, case .unique = arguments.gift, let togglePinnedToTop, let pinnedToTop = arguments.pinnedToTop { + items.append(.action(ContextMenuActionItem(text: pinnedToTop ? strings.PeerInfo_Gifts_Context_Unpin : strings.PeerInfo_Gifts_Context_Pin , icon: { theme in generateTintedImage(image: UIImage(bundleImageName: pinnedToTop ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in + c?.dismiss(completion: { [weak self] in + guard let self else { + return + } + + let pinnedToTop = !pinnedToTop + if togglePinnedToTop(pinnedToTop) { + if pinnedToTop { + self.dismissAnimated() + } else { + let toastText = strings.PeerInfo_Gifts_ToastUnpinned_Text + self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_toastunpin", scale: 0.06, colors: [:], title: nil, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + if case let .profileGift(peerId, gift) = self.subject { + self.subject = .profileGift(peerId, gift.withPinnedToTop(false)) + } + } + } else { + var maxPinnedCount: Int = 6 + if let value = context.currentAppConfiguration.with({ $0 }).data?["stargifts_pinned_to_top_limit"] as? Double { + maxPinnedCount = Int(value) + } + self.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: strings.PeerInfo_Gifts_ToastPinLimit_Text(Int32(maxPinnedCount)), timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + }) + }))) + } + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_View_Context_CopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 05326dd7e0..c4ae4a17fe 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -4855,9 +4855,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, transferGift: { [weak profileGifts] prepaid, peerId in guard let profileGifts, let reference = gift.reference else { - return + return .complete() } - profileGifts.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId) + return profileGifts.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId) }, upgradeGift: { [weak profileGifts] formId, keepOriginalInfo in guard let profileGifts, let reference = gift.reference else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index c0709e9684..d10b3eb964 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -518,9 +518,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr }, transferGift: { [weak self] prepaid, peerId in guard let self, let reference = product.reference else { - return + return .complete() } - self.profileGifts.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId) + return self.profileGifts.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId) }, upgradeGift: { [weak self] formId, keepOriginalInfo in guard let self, let reference = product.reference else { @@ -528,6 +528,27 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } return self.profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo) }, + togglePinnedToTop: { [weak self] pinnedToTop in + guard let self else { + return false + } + if pinnedToTop && self.pinnedReferences.count >= self.maxPinnedCount { + return false + } + if let reference = product.reference { + self.profileGifts.updateStarGiftPinnedToTop(reference: reference, pinnedToTop: pinnedToTop) + + if pinnedToTop { + let _ = self.scrollToTop() + Queue.mainQueue().after(0.35) { + let toastTitle = params.presentationData.strings.PeerInfo_Gifts_ToastPinned_Title + let toastText = params.presentationData.strings.PeerInfo_Gifts_ToastPinned_Text + self.parentController?.present(UndoOverlayController(presentationData: params.presentationData, content: .universal(animation: "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + } + } + } + return true + }, shareStory: { [weak self] uniqueGift in guard let self, let parentController = self.parentController else { return @@ -977,7 +998,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let pinnedToTop = !gift.pinnedToTop if pinnedToTop && self.pinnedReferences.count >= self.maxPinnedCount { - self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.PeerInfo_Gifts_ToastPinLimit_Text(Int32(self.maxPinnedCount)), timeout: nil, customUndoText: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: strings.PeerInfo_Gifts_ToastPinLimit_Text(Int32(self.maxPinnedCount)), timeout: nil, customUndoText: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) return } @@ -989,10 +1010,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let toastText: String if !pinnedToTop { toastTitle = nil - toastText = presentationData.strings.PeerInfo_Gifts_ToastUnpinned_Text + toastText = strings.PeerInfo_Gifts_ToastUnpinned_Text } else { - toastTitle = presentationData.strings.PeerInfo_Gifts_ToastPinned_Title - toastText = presentationData.strings.PeerInfo_Gifts_ToastPinned_Text + toastTitle = strings.PeerInfo_Gifts_ToastPinned_Title + toastText = strings.PeerInfo_Gifts_ToastPinned_Text } self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: !pinnedToTop ? "anim_toastunpin" : "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) }) @@ -1200,14 +1221,14 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let transferStars = gift.transferStars ?? 0 let controller = context.sharedContext.makePremiumGiftController(context: context, source: .starGiftTransfer(birthdays, reference, uniqueGift, transferStars, gift.canExportDate, showSelf), completion: { [weak self] peerIds in guard let self, let peerId = peerIds.first else { - return + return .complete() } - self.profileGifts.transferStarGift(prepaid: transferStars == 0, reference: reference, peerId: peerId) - Queue.mainQueue().after(1.0, { + Queue.mainQueue().after(1.5, { if transferStars > 0 { context.starsContext?.load(force: true) } }) + return self.profileGifts.transferStarGift(prepaid: transferStars == 0, reference: reference, peerId: peerId) }) self.parentController?.push(controller) }) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 77f66c4b15..e3c287fa6e 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2674,7 +2674,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return controller } - public func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController { + public func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Signal)?) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } var presentExportAlertImpl: (() -> Void)? @@ -2912,6 +2912,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _, _) = source else { return } + var dismissAlertImpl: (() -> Void)? let alertController = giftTransferAlertController( context: context, gift: gift, @@ -2920,61 +2921,89 @@ public final class SharedAccountContextImpl: SharedAccountContext { navigationController: controller.navigationController as? NavigationController, commit: { [weak controller] in let proceed: (Bool) -> Void = { waitForTopUp in - completion?([peer.id]) - guard let controller, let navigationController = controller.navigationController as? NavigationController else { return } - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is ContactSelectionController) } - if !isChannelGift { - if peer.id.namespace == Namespaces.Peer.CloudChannel { - if let controller = context.sharedContext.makePeerInfoController( - context: context, - updatedPresentationData: nil, - peer: peer._asPeer(), - mode: .gifts, - avatarInitiallyExpanded: false, - fromChat: false, - requestsContext: nil - ) { - controllers.append(controller) + if let completion { + let _ = (completion([peer.id]) + |> deliverOnMainQueue).startStandalone(error: { [weak navigationController] error in + guard let navigationController else { + return } - } else { - var foundController = false - for controller in controllers.reversed() { - if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation { - chatController.hintPlayNextOutgoingGift() - foundController = true - break + dismissAlertImpl?() + + var errorText: String? + switch error { + case .disallowedStarGift: + errorText = presentationData.strings.Gift_Send_ErrorDisallowed(peer.compactDisplayTitle).string + default: + errorText = presentationData.strings.Gift_Send_ErrorUnknown + } + + if let errorText = errorText { + let alertController = textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true) + if let lastController = navigationController.viewControllers.last as? ViewController { + lastController.present(alertController, in: .window(.root)) } } - if !foundController { - let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil) - chatController.hintPlayNextOutgoingGift() - controllers.append(chatController) + }, completed: { [weak navigationController] in + guard let navigationController else { + return } - } - } - navigationController.setViewControllers(controllers, animated: true) - - Queue.mainQueue().after(0.3) { - let tooltipController = UndoOverlayController( - presentationData: presentationData, - content: .forward(savedMessages: false, text: presentationData.strings.Gift_Transfer_Success("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))", peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string), - elevatedLayout: false, - action: { _ in return true } - ) - if let lastController = controllers.last as? ViewController { - lastController.present(tooltipController, in: .window(.root)) - } - - Queue.mainQueue().after(0.5) { + dismissAlertImpl?() + var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is GiftViewScreen) } - navigationController.setViewControllers(controllers, animated: false) - } + controllers = controllers.filter { !($0 is ContactSelectionController) } + if !isChannelGift { + if peer.id.namespace == Namespaces.Peer.CloudChannel { + if let controller = context.sharedContext.makePeerInfoController( + context: context, + updatedPresentationData: nil, + peer: peer._asPeer(), + mode: .gifts, + avatarInitiallyExpanded: false, + fromChat: false, + requestsContext: nil + ) { + controllers.append(controller) + } + } else { + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation { + chatController.hintPlayNextOutgoingGift() + foundController = true + break + } + } + if !foundController { + let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + chatController.hintPlayNextOutgoingGift() + controllers.append(chatController) + } + } + } + navigationController.setViewControllers(controllers, animated: true) + + Queue.mainQueue().after(0.3) { + let tooltipController = UndoOverlayController( + presentationData: presentationData, + content: .forward(savedMessages: false, text: presentationData.strings.Gift_Transfer_Success("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))", peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string), + elevatedLayout: false, + action: { _ in return true } + ) + if let lastController = navigationController.viewControllers.last as? ViewController { + lastController.present(tooltipController, in: .window(.root)) + } + + Queue.mainQueue().after(0.5) { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftViewScreen) } + navigationController.setViewControllers(controllers, animated: false) + } + } + }) } } @@ -3005,6 +3034,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { } ) controller.present(alertController, in: .window(.root)) + + dismissAlertImpl = { [weak alertController] in + alertController?.dismissAnimated() + } } return controller