diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 1f8fd53658..501e54bf15 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -568,6 +568,7 @@ public enum PeerInfoControllerMode { case forumTopic(thread: ChatReplyThreadMessage) case recommendedChannels case myProfile + case myProfileGifts } public enum ContactListActionItemInlineIconPosition { @@ -975,6 +976,8 @@ public protocol SharedAccountContext: AnyObject { func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController 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 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/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index b297f7ba13..da2930b8c2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -191,7 +191,7 @@ func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) -> func managedStarGiftsUpdates(postbox: Postbox, network: Network) -> Signal { let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network) - return (poll |> then(.complete() |> suspendAwareDelay(2.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart + return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } func _internal_convertStarGift(account: Account, messageId: EngineMessage.Id) -> Signal { diff --git a/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift b/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift index b3574cd921..34da6c35b2 100644 --- a/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift +++ b/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift @@ -228,7 +228,8 @@ private final class SheetPageContent: CombinedComponent { component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.ReportAd_Help_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) } )), - items: items + items: items, + isModal: true ), environment: {}, availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 6f86b3e5d8..e356eab682 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -332,6 +332,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { case let .starGift(gift, convertStars, giftText, entities, nameHidden, savedToProfile, converted): let _ = nameHidden //TODO:localize + if !incoming { + buttonTitle = "" + } let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" title = "Gift from \(authorName)" if let giftText, !giftText.isEmpty { @@ -397,8 +400,11 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - - giftSize.height = titleLayout.size.height + textSpacing + subtitleLayout.size.height + 212.0 + + giftSize.height = titleLayout.size.height + textSpacing + subtitleLayout.size.height + 164.0 + if !buttonTitle.isEmpty { + giftSize.height += 48.0 + } var labelRects = labelLayout.linesRects() if labelRects.count > 1 { @@ -458,6 +464,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let animationFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0 + iconOffset), size: iconSize) strongSelf.animationNode.frame = animationFrame + strongSelf.buttonNode.isHidden = buttonTitle.isEmpty + strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty + if strongSelf.item == nil { strongSelf.animationNode.started = { [weak self] in if let strongSelf = self { diff --git a/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift b/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift index a248532726..383aecb2c4 100644 --- a/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift +++ b/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift @@ -162,7 +162,7 @@ private final class SheetPageContent: CombinedComponent { transition: .immediate ) context.add(back - .position(CGPoint(x: sideInset + back.size.width / 2.0 - (component.title != nil ? 8.0 : 0.0), y: contentSize.height + back.size.height / 2.0)) + .position(CGPoint(x: sideInset + back.size.width / 2.0 - (!component.isFirst ? 8.0 : 0.0), y: contentSize.height + back.size.height / 2.0)) ) let constrainedTitleWidth = context.availableSize.width - (back.size.width + 16.0) * 2.0 @@ -280,7 +280,8 @@ private final class SheetPageContent: CombinedComponent { maximumNumberOfLines: 0 )), footer: footer, - items: items + items: items, + isModal: true ), environment: {}, availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), @@ -696,12 +697,10 @@ public final class ContentReportScreen: ViewControllerComponentContainer { switch result { case .reported: - Queue.mainQueue().after(0.1) { - completed() - } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } Queue.mainQueue().after(0.4, { + completed() + (navigationController?.viewControllers.last as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(name: "PoliceCar", text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return true }), in: .current) }) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index d81772d75b..d7c14b4bc6 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -82,7 +82,10 @@ private final class GiftViewSheetContent: CombinedComponent { super.init() if let arguments = subject.arguments { - let peerIds: [EnginePeer.Id] = [arguments.peerId, context.account.peerId] + var peerIds: [EnginePeer.Id] = [arguments.peerId, context.account.peerId] + if let fromPeerId = arguments.fromPeerId { + peerIds.append(fromPeerId) + } self.disposable = (context.engine.data.get( EngineDataMap( peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in @@ -204,7 +207,11 @@ private final class GiftViewSheetContent: CombinedComponent { descriptionText = "You converted this gift to \(convertStars) Stars. [More About Stars >]()" } } else if let peerId = component.subject.arguments?.peerId, let peer = state.peerMap[peerId] { - descriptionText = "\(peer.compactDisplayTitle) can keep this gift in their Profile or convert it to \(convertStars) Stars. [More About Stars >]()" + if case .message = component.subject { + descriptionText = "\(peer.compactDisplayTitle) can keep this gift in their Profile or convert it to \(convertStars) Stars. [More About Stars >]()" + } else { + descriptionText = "" + } } else { descriptionText = "" } @@ -273,10 +280,10 @@ private final class GiftViewSheetContent: CombinedComponent { let tableLinkColor = theme.list.itemAccentColor var tableItems: [TableComponent.Item] = [] - if let peerId = component.subject.arguments?.peerId, let peer = state.peerMap[peerId] { + if let peerId = component.subject.arguments?.fromPeerId, let peer = state.peerMap[peerId] { tableItems.append(.init( - id: "to", - title: incoming ? strings.Stars_Transaction_From : strings.Stars_Transaction_To, + id: "from", + title: strings.Stars_Transaction_From, component: AnyComponent( Button( content: AnyComponent( @@ -287,24 +294,24 @@ private final class GiftViewSheetContent: CombinedComponent { ) ), action: { - if "".isEmpty { - component.openPeer(peer) - Queue.mainQueue().after(1.0, { - component.cancel(false) - }) - } else { +// 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) - } +// } } ) ) )) } else { tableItems.append(.init( - id: "from", + id: "from_anon", title: strings.Stars_Transaction_From, component: AnyComponent( PeerCellComponent( @@ -430,6 +437,8 @@ private final class GiftViewSheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: descriptionOrigin + description.size.height / 2.0)) ) originY += description.size.height + 10.0 + } else { + originY += 11.0 } let amountSpacing: CGFloat = 1.0 @@ -439,7 +448,11 @@ private final class GiftViewSheetContent: CombinedComponent { var amountOrigin = originY if "".isEmpty { amountOrigin -= descriptionSize.height + 10.0 - originY += amount.size.height + 26.0 + if descriptionSize.height > 0 { + originY += amount.size.height + 26.0 + } else { + originY += amount.size.height + 2.0 + } } else { originY += amount.size.height + 20.0 } @@ -696,14 +709,14 @@ public class GiftViewScreen: ViewControllerComponentContainer { case message(EngineMessage) case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift) - var arguments: (peerId: EnginePeer.Id, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, convertStars: Int64, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool)? { + var arguments: (peerId: EnginePeer.Id, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, convertStars: Int64, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool)? { switch self { case let .message(message): if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted) = action.action { - return (message.id.peerId, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, convertStars, text, entities, nameHidden, savedToProfile, converted) + return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, convertStars, text, entities, nameHidden, savedToProfile, converted) } case let .profileGift(peerId, gift): - return (peerId, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.convertStars ?? 0, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false) + return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.convertStars ?? 0, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false) } return nil } @@ -792,9 +805,25 @@ public class GiftViewScreen: ViewControllerComponentContainer { 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), elevatedLayout: lastController is ChatController, - action: { action in - if case .info = action { - + action: { [weak navigationController] action in + if case .info = action, let navigationController { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak navigationController] peer in + guard let peer, let navigationController else { + return + } + if let controller = context.sharedContext.makePeerInfoController( + context: context, + updatedPresentationData: nil, + peer: peer._asPeer(), + mode: .myProfileGifts, + avatarInitiallyExpanded: false, + fromChat: false, + requestsContext: nil + ) { + navigationController.pushViewController(controller, animated: true) + } + }) } return true } @@ -823,8 +852,12 @@ public class GiftViewScreen: ViewControllerComponentContainer { |> deliverOnMainQueue).startStandalone() } 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 lastController = navigationController.viewControllers.last as? ViewController { let resultController = UndoOverlayController( diff --git a/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift b/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift index b8092ada4f..8f3c68384c 100644 --- a/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift +++ b/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift @@ -41,17 +41,20 @@ public final class ListSectionContentView: UIView { public final class Configuration { public let theme: PresentationTheme + public let isModal: Bool public let displaySeparators: Bool public let extendsItemHighlightToSection: Bool public let background: ListSectionComponent.Background public init( theme: PresentationTheme, + isModal: Bool = false, displaySeparators: Bool, extendsItemHighlightToSection: Bool, background: ListSectionComponent.Background ) { self.theme = theme + self.isModal = isModal self.displaySeparators = displaySeparators self.extendsItemHighlightToSection = extendsItemHighlightToSection self.background = background @@ -116,7 +119,7 @@ public final class ListSectionContentView: UIView { backgroundColor = configuration.theme.list.itemHighlightedBackgroundColor } else { transition = .easeInOut(duration: 0.2) - backgroundColor = configuration.theme.list.itemBlocksBackgroundColor + backgroundColor = configuration.isModal ? configuration.theme.list.itemModalBlocksBackgroundColor : configuration.theme.list.itemBlocksBackgroundColor } self.externalContentBackgroundView.updateColor(color: backgroundColor, transition: transition) @@ -144,7 +147,7 @@ public final class ListSectionContentView: UIView { if self.highlightedItemId != nil && configuration.extendsItemHighlightToSection { backgroundColor = configuration.theme.list.itemHighlightedBackgroundColor } else { - backgroundColor = configuration.theme.list.itemBlocksBackgroundColor + backgroundColor = configuration.isModal ? configuration.theme.list.itemModalBlocksBackgroundColor : configuration.theme.list.itemBlocksBackgroundColor } self.externalContentBackgroundView.updateColor(color: backgroundColor, transition: transition) @@ -305,6 +308,7 @@ public final class ListSectionComponent: Component { public let header: AnyComponent? public let footer: AnyComponent? public let items: [AnyComponentWithIdentity] + public let isModal: Bool public let displaySeparators: Bool public let extendsItemHighlightToSection: Bool @@ -314,6 +318,7 @@ public final class ListSectionComponent: Component { header: AnyComponent?, footer: AnyComponent?, items: [AnyComponentWithIdentity], + isModal: Bool = false, displaySeparators: Bool = true, extendsItemHighlightToSection: Bool = false ) { @@ -322,6 +327,7 @@ public final class ListSectionComponent: Component { self.header = header self.footer = footer self.items = items + self.isModal = isModal self.displaySeparators = displaySeparators self.extendsItemHighlightToSection = extendsItemHighlightToSection } @@ -342,6 +348,9 @@ public final class ListSectionComponent: Component { if lhs.items != rhs.items { return false } + if lhs.isModal != rhs.isModal { + return false + } if lhs.displaySeparators != rhs.displaySeparators { return false } @@ -448,6 +457,7 @@ public final class ListSectionComponent: Component { let contentResult = self.contentView.update( configuration: ListSectionContentView.Configuration( theme: component.theme, + isModal: component.isModal, displaySeparators: component.displaySeparators, extendsItemHighlightToSection: component.extendsItemHighlightToSection, background: component.background @@ -522,17 +532,20 @@ public final class ListSubSectionComponent: Component { public let theme: PresentationTheme public let leftInset: CGFloat public let items: [AnyComponentWithIdentity] + public let isModal: Bool public let displaySeparators: Bool public init( theme: PresentationTheme, leftInset: CGFloat, items: [AnyComponentWithIdentity], + isModal: Bool = false, displaySeparators: Bool = true ) { self.theme = theme self.leftInset = leftInset self.items = items + self.isModal = isModal self.displaySeparators = displaySeparators } @@ -546,6 +559,9 @@ public final class ListSubSectionComponent: Component { if lhs.items != rhs.items { return false } + if lhs.isModal != rhs.isModal { + return false + } if lhs.displaySeparators != rhs.displaySeparators { return false } @@ -615,6 +631,7 @@ public final class ListSubSectionComponent: Component { let contentResult = self.contentView.update( configuration: ListSectionContentView.Configuration( theme: component.theme, + isModal: component.isModal, displaySeparators: component.displaySeparators, extendsItemHighlightToSection: false, background: .none(clipped: false) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index e9a5f9f5c9..8d4948be80 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -8250,9 +8250,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private func openReport(type: PeerInfoReportType, contextController: ContextControllerProtocol?, backAction: ((ContextControllerProtocol) -> Void)?) { - guard let controller = self.controller else { - return - } self.view.endEditing(true) switch type { @@ -8291,20 +8288,28 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro ]) self.controller?.present(actionSheet, in: .window(.root)) default: - let options: [PeerReportOption] - if case .user = type { - options = [.spam, .fake, .violence, .pornography, .childAbuse] - } else { - options = [.spam, .fake, .violence, .pornography, .childAbuse, .copyright, .other] - } + contextController?.dismiss() - presentPeerReportOptions(context: self.context, parent: controller, contextController: contextController, backAction: backAction, subject: .peer(self.peerId), options: options, passthrough: true, completion: { [weak self] reason, _ in - if let reason = reason { - DispatchQueue.main.async { - self?.openChatForReporting(reason) - } - } + self.context.sharedContext.makeContentReportScreen(context: self.context, subject: .peer(self.peerId), forceDark: false, present: { [weak self] controller in + self?.controller?.push(controller) + }, completion: { + }) + +// let options: [PeerReportOption] +// if case .user = type { +// options = [.spam, .fake, .violence, .pornography, .childAbuse] +// } else { +// options = [.spam, .fake, .violence, .pornography, .childAbuse, .copyright, .other] +// } +// +// presentPeerReportOptions(context: self.context, parent: controller, contextController: contextController, backAction: backAction, subject: .peer(self.peerId), options: options, passthrough: true, completion: { [weak self] reason, _ in +// if let reason = reason { +// DispatchQueue.main.async { +// self?.openChatForReporting(reason) +// } +// } +// }) } } @@ -11645,13 +11650,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } strongSelf.view.endEditing(true) - strongSelf.controller?.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), passthrough: false, present: { c, a in - self?.controller?.present(c, in: .window(.root), with: a) - }, push: { c in - self?.controller?.push(c) - }, completion: { _, _ in }), in: .window(.root)) - - + strongSelf.context.sharedContext.makeContentReportScreen(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), forceDark: false, present: { [weak self] controller in + self?.controller?.push(controller) + }, completion: {}) }, displayCopyProtectionTip: { [weak self] node, save in if let strongSelf = self, let peer = strongSelf.data?.peer, let messageIds = strongSelf.state.selectedMessageIds, !messageIds.isEmpty { let _ = (strongSelf.context.engine.data.get(EngineDataMap( @@ -12284,6 +12285,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc private weak var requestsContext: PeerInvitationImportersContext? fileprivate let starsContext: StarsContext? private let switchToRecommendedChannels: Bool + private let switchToGifts: Bool private let chatLocation: ChatLocation private let chatLocationContextHolder = Atomic(value: nil) @@ -12340,7 +12342,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, isMyProfile: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, isMyProfile: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false, switchToGifts: Bool = false) { self.context = context self.updatedPresentationData = updatedPresentationData self.peerId = peerId @@ -12354,6 +12356,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc self.hintGroupInCommon = hintGroupInCommon self.requestsContext = requestsContext self.switchToRecommendedChannels = switchToRecommendedChannels + self.switchToGifts = switchToGifts if let forumTopicThread = forumTopicThread { self.chatLocation = .replyThread(message: forumTopicThread) @@ -12694,7 +12697,13 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc } override public func loadDisplayNode() { - self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, starsContext: self.starsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: self.switchToRecommendedChannels ? .recommended : nil) + var initialPaneKey: PeerInfoPaneKey? + if self.switchToRecommendedChannels { + initialPaneKey = .recommended + } else if self.switchToGifts { + initialPaneKey = .gifts + } + self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, starsContext: self.starsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: initialPaneKey) self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 }) self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get()) self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get()) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index f99b353467..ff9d282d3e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -87,6 +87,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr guard let self else { return } + let isFirstTime = starsProducts == nil let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } self.statusPromise.set(.single(PeerInfoStatusData(text: presentationData.strings.SharedMedia_GiftCount(state.count ?? 0), isActivity: true, key: .gifts))) self.starsProducts = state.gifts @@ -96,7 +97,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.ready.set(.single(true)) } - self.updateScrolling() + self.updateScrolling(transition: isFirstTime ? .immediate : .easeInOut(duration: 0.25)) }) } @@ -119,10 +120,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } public func scrollViewDidScroll(_ scrollView: UIScrollView) { - self.updateScrolling() + self.updateScrolling(transition: .immediate) } - func updateScrolling() { + func updateScrolling(transition: ComponentTransition) { if let starsProducts = self.starsProducts, let params = self.currentParams { let optionSpacing: CGFloat = 10.0 let sideInset = params.sideInset + 16.0 @@ -140,13 +141,14 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let itemId = AnyHashable(product.date) validIds.append(itemId) - let itemTransition = ComponentTransition.immediate + var itemTransition = transition let visibleItem: ComponentView if let current = self.starsItems[itemId] { visibleItem = current } else { visibleItem = ComponentView() self.starsItems[itemId] = visibleItem + itemTransition = .immediate } var isVisible = false @@ -221,6 +223,26 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } } + var removeIds: [AnyHashable] = [] + for (id, item) in self.starsItems { + if !validIds.contains(id) { + removeIds.append(id) + if let itemView = item.view { + if !transition.animation.isImmediate { + itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false) + itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + itemView.removeFromSuperview() + }) + } else { + itemView.removeFromSuperview() + } + } + } + } + for id in removeIds { + self.starsItems.removeValue(forKey: id) + } + var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * starsOptionSize.height + 60.0 + 16.0 if self.peerId == self.context.account.peerId { @@ -354,7 +376,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } self.scrollNode.view.isScrollEnabled = !isScrollingLockedAtTop - self.updateScrolling() + self.updateScrolling(transition: ComponentTransition(transition)) } public func findLoadedMessage(id: MessageId) -> Message? { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 7248d88a8e..52d550b52c 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -1037,7 +1037,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { guard let self else { return } - let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .stars(birthdays), completion: { [weak self] peerIds in + 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 } diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 12a9161dfd..35c5a4e6c3 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -741,6 +741,9 @@ public final class TextFieldComponent: Component { } self.insertText(NSAttributedString(string: insertString)) + } else if (range.length == 0 && text == "\n"), let returnKeyAction = component.returnKeyAction { + returnKeyAction() + return false } return false } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 5d212212f5..2baa737ced 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2204,6 +2204,75 @@ public final class SharedAccountContextImpl: SharedAccountContext { return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action) } + public func makeStarsGiftController(context: AccountContext, birthdays: [EnginePeer.Id: TelegramBirthday]?, completion: @escaping (([EnginePeer.Id]) -> Void)) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + var presentBirthdayPickerImpl: (() -> Void)? + let starsMode: ContactSelectionControllerMode = .starsGifting(birthdays: birthdays, hasActions: false) + + let contactOptions: Signal<[ContactListAdditionalOption], NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)) + |> map { birthday in + if birthday == nil { + return [ContactListAdditionalOption( + title: presentationData.strings.Premium_Gift_ContactSelection_AddBirthday, + icon: .generic(UIImage(bundleImageName: "Contact List/AddBirthdayIcon")!), + action: { + presentBirthdayPickerImpl?() + }, + clearHighlightAutomatically: true + )] + } else { + return [] + } + } + |> deliverOnMainQueue + + let options = Promise<[StarsGiftOption]>() + options.set(context.engine.payments.starsGiftOptions(peerId: nil)) + let controller = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams( + context: context, + mode: starsMode, + autoDismiss: false, + title: { strings in return strings.Stars_Purchase_GiftStars }, + options: contactOptions + )) + let _ = (controller.result + |> deliverOnMainQueue).start(next: { result in + if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer { + completion([peer.id]) + } + }) + + presentBirthdayPickerImpl = { [weak controller] in + guard let controller else { + return + } + let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .setupBirthday).startStandalone() + + let settingsPromise: Promise + if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface, let current = rootController.getPrivacySettings() { + settingsPromise = current + } else { + settingsPromise = Promise() + settingsPromise.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init))) + } + let birthdayController = BirthdayPickerScreen(context: context, settings: settingsPromise.get(), openSettings: { + context.sharedContext.makeBirthdayPrivacyController(context: context, settings: settingsPromise, openedFromBirthdayScreen: true, present: { [weak controller] c in + controller?.push(c) + }) + }, completion: { [weak controller] value in + let _ = context.engine.accountData.updateBirthday(birthday: value).startStandalone() + + controller?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: presentationData.strings.Birthday_Added, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in + return true + }), in: .current) + }) + controller.push(birthdayController) + } + + return controller + } + public func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -2247,122 +2316,37 @@ public final class SharedAccountContextImpl: SharedAccountContext { var sendMessageImpl: ((EnginePeer) -> Void)? //TODO:localize - let controller: ViewController -// if case .stars = source { -// let options = Promise<[StarsGiftOption]>() -// options.set(context.engine.payments.starsGiftOptions(peerId: nil)) - let options = Promise<[PremiumGiftCodeOption]>() - options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil)) - let contactsController = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams( - context: context, - mode: starsMode, - autoDismiss: false, - title: { strings in return "Gift Premium or Stars" }, - options: contactOptions, - openProfile: { peer in - openProfileImpl?(peer) - }, - sendMessage: { peer in - sendMessageImpl?(peer) - } - )) - let _ = combineLatest(queue: Queue.mainQueue(), contactsController.result, options.get()) - .startStandalone(next: { [weak contactsController] result, options in - if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext { - let premiumOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } - let giftController = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumOptions) - giftController.navigationPresentation = .modal - contactsController?.push(giftController) - + let options = Promise<[PremiumGiftCodeOption]>() + options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil)) + let controller = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams( + context: context, + mode: starsMode, + autoDismiss: false, + title: { strings in return "Gift Premium or Stars" }, + options: contactOptions, + openProfile: { peer in + openProfileImpl?(peer) + }, + sendMessage: { peer in + sendMessageImpl?(peer) + } + )) + let _ = combineLatest(queue: Queue.mainQueue(), contactsController.result, options.get()) + .startStandalone(next: { [weak controller] result, options in + if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext { + let premiumOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } + let giftController = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumOptions) + giftController.navigationPresentation = .modal + controller?.push(giftController) + // completion?([peer.id]) - - if case .chatList = source, let _ = currentBirthdays { - let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .todayBirthdays).startStandalone() - } + + if case .chatList = source, let _ = currentBirthdays { + let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .todayBirthdays).startStandalone() } - }) - controller = contactsController -// } else { -// let options = Promise<[PremiumGiftCodeOption]>() -// options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil)) -// let contactsController = context.sharedContext.makeContactMultiselectionController( -// ContactMultiselectionControllerParams( -// context: context, -// mode: mode, -// options: contactOptions, -// isPeerEnabled: { peer in -// if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) { -// return true -// } else { -// return false -// } -// }, -// limit: limit, -// reachedLimit: { limit in -// reachedLimitImpl?(limit) -// }, -// openProfile: { peer in -// openProfileImpl?(peer) -// }, -// sendMessage: { peer in -// sendMessageImpl?(peer) -// } -// ) -// ) -// let _ = combineLatest(queue: Queue.mainQueue(), contactsController.result, options.get()) -// .startStandalone(next: { [weak contactsController] result, options in -// guard let controller = contactsController else { -// return -// } -// var peerIds: [PeerId] = [] -// if case let .result(peerIdsValue, _) = result { -// peerIds = peerIdsValue.compactMap({ peerId in -// if case let .peer(peerId) = peerId { -// return peerId -// } else { -// return nil -// } -// }) -// } -// guard !peerIds.isEmpty else { -// return -// } -// -// let mappedOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } -// var pushImpl: ((ViewController) -> Void)? -// var filterImpl: (() -> Void)? -// let giftController = PremiumGiftScreen(context: context, peerIds: peerIds, options: mappedOptions, source: source, pushController: { c in -// pushImpl?(c) -// }, completion: { -// filterImpl?() -// -// if case .chatList = source, let _ = currentBirthdays { -// let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .todayBirthdays).startStandalone() -// } -// }) -// pushImpl = { [weak giftController] c in -// giftController?.push(c) -// } -// filterImpl = { [weak giftController] in -// if let navigationController = giftController?.navigationController as? NavigationController { -// var controllers = navigationController.viewControllers -// controllers = controllers.filter { !($0 is ContactMultiselectionController) && !($0 is PremiumGiftScreen) } -// navigationController.setViewControllers(controllers, animated: true) -// } -// } -// controller.push(giftController) -// }) -// controller = contactsController -// } + } + }) -// reachedLimitImpl = { [weak controller] limit in -// guard let controller else { -// return -// } -// HapticFeedback().error() -// controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Premium_Gift_ContactSelection_MaximumReached("\(limit)").string, timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current) -// } - sendMessageImpl = { [weak self, weak controller] peer in guard let self, let controller, let navigationController = controller.navigationController as? NavigationController else { return @@ -2864,6 +2848,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation var hintGroupInCommon: PeerId? var forumTopicThread: ChatReplyThreadMessage? var isMyProfile = false + var switchToGifts = false switch mode { case let .nearbyPeer(distance): @@ -2880,10 +2865,13 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation forumTopicThread = thread case .myProfile: isMyProfile = true + case .myProfileGifts: + isMyProfile = true + switchToGifts = true default: break } - return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread) + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, switchToGifts: switchToGifts) } else if peer is TelegramSecretChat { return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: []) }