diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 65d1e9bb7a..dd7cb77078 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12297,4 +12297,6 @@ Sorry for the inconvenience."; "Stars.Transfer.Balance" = "Balance"; +"Stars.Transfer.Unavailable" = "Unavailable"; + "Settings.Stars" = "Your Stars"; diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index 2373353de5..d847a744d4 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -122,6 +122,7 @@ public struct PremiumConfiguration { public static var defaultValue: PremiumConfiguration { return PremiumConfiguration( isPremiumDisabled: false, + areStarsDisabled: true, subscriptionManagementUrl: "", showPremiumGiftInAttachMenu: false, showPremiumGiftInTextField: false, @@ -147,6 +148,7 @@ public struct PremiumConfiguration { } public let isPremiumDisabled: Bool + public let areStarsDisabled: Bool public let subscriptionManagementUrl: String public let showPremiumGiftInAttachMenu: Bool public let showPremiumGiftInTextField: Bool @@ -171,6 +173,7 @@ public struct PremiumConfiguration { fileprivate init( isPremiumDisabled: Bool, + areStarsDisabled: Bool, subscriptionManagementUrl: String, showPremiumGiftInAttachMenu: Bool, showPremiumGiftInTextField: Bool, @@ -194,6 +197,7 @@ public struct PremiumConfiguration { minGroupAudioTranscriptionLevel: Int32 ) { self.isPremiumDisabled = isPremiumDisabled + self.areStarsDisabled = areStarsDisabled self.subscriptionManagementUrl = subscriptionManagementUrl self.showPremiumGiftInAttachMenu = showPremiumGiftInAttachMenu self.showPremiumGiftInTextField = showPremiumGiftInTextField @@ -225,6 +229,7 @@ public struct PremiumConfiguration { } return PremiumConfiguration( isPremiumDisabled: data["premium_purchase_blocked"] as? Bool ?? defaultValue.isPremiumDisabled, + areStarsDisabled: data["stars_purchase_blocked"] as? Bool ?? defaultValue.areStarsDisabled, subscriptionManagementUrl: data["premium_manage_subscription_url"] as? String ?? "", showPremiumGiftInAttachMenu: data["premium_gift_attach_menu_icon"] as? Bool ?? defaultValue.showPremiumGiftInAttachMenu, showPremiumGiftInTextField: data["premium_gift_text_field_icon"] as? Bool ?? defaultValue.showPremiumGiftInTextField, diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift index 6012db359d..4b5ed063af 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift @@ -9,7 +9,7 @@ import ChatListUI import SegmentedControlNode import ChatListSearchItemHeader -final class HashtagSearchControllerNode: ASDisplayNode { +final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { private let context: AccountContext private weak var controller: HashtagSearchController? private var query: String @@ -26,6 +26,8 @@ final class HashtagSearchControllerNode: ASDisplayNode { private let isSearching = Promise() private var isSearchingDisposable: Disposable? + private let clippingNode: ASDisplayNode + private let containerNode: ASDisplayNode let currentController: ChatController? let myController: ChatController? let myChatContents: HashtagSearchGlobalChatContents? @@ -33,6 +35,8 @@ final class HashtagSearchControllerNode: ASDisplayNode { let globalController: ChatController? let globalChatContents: HashtagSearchGlobalChatContents? + private var panRecognizer: InteractiveTransitionGestureRecognizer? + private var containerLayout: (ContainerViewLayout, CGFloat)? private var hasValidLayout = false @@ -44,6 +48,11 @@ final class HashtagSearchControllerNode: ASDisplayNode { let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.clippingNode = ASDisplayNode() + self.clippingNode.clipsToBounds = true + + self.containerNode = ASDisplayNode() + let cleanHashtag = query.replacingOccurrences(of: "#", with: "") self.searchContentNode = HashtagSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, initialQuery: cleanHashtag, hasCurrentChat: peer != nil, cancel: { [weak controller] in controller?.dismiss() @@ -90,19 +99,14 @@ final class HashtagSearchControllerNode: ASDisplayNode { }) self.backgroundColor = presentationData.theme.chatList.backgroundColor + + self.addSubnode(self.clippingNode) + self.clippingNode.addSubnode(self.containerNode) if controller.all { - self.currentController?.displayNode.isHidden = true - self.myController?.displayNode.isHidden = false - self.globalController?.displayNode.isHidden = true - self.isSearching.set(self.myChatContents?.searching ?? .single(false)) } else { if let _ = peer { - self.currentController?.displayNode.isHidden = false - self.myController?.displayNode.isHidden = true - self.globalController?.displayNode.isHidden = true - let isSearching: Signal if let currentController = self.currentController { isSearching = .single(true) @@ -115,9 +119,6 @@ final class HashtagSearchControllerNode: ASDisplayNode { } self.isSearching.set(isSearching) } else { - self.myController?.displayNode.isHidden = false - self.globalController?.displayNode.isHidden = true - self.isSearching.set(self.myChatContents?.searching ?? .single(false)) } } @@ -128,21 +129,15 @@ final class HashtagSearchControllerNode: ASDisplayNode { } self.searchContentNode.selectedIndex = index if index == 0 { - self.currentController?.displayNode.isHidden = false - self.myController?.displayNode.isHidden = true - self.globalController?.displayNode.isHidden = true self.isSearching.set(self.currentController?.searching.get() ?? .single(false)) } else if index == 1 { - self.currentController?.displayNode.isHidden = true - self.myController?.displayNode.isHidden = false - self.globalController?.displayNode.isHidden = true self.isSearching.set(self.myChatContents?.searching ?? .single(false)) } else if index == 2 { - self.currentController?.displayNode.isHidden = true - self.myController?.displayNode.isHidden = true - self.globalController?.displayNode.isHidden = false self.isSearching.set(self.globalChatContents?.searching ?? .single(false)) } + if let (layout, navigationHeight) = self.containerLayout { + let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + } } self.recentListNode.setSearchQuery = { [weak self] query in @@ -209,6 +204,125 @@ final class HashtagSearchControllerNode: ASDisplayNode { self.isSearchingDisposable?.dispose() } + private var panAllowedDirections: InteractiveTransitionGestureRecognizerDirections { + let currentIndex = self.searchContentNode.selectedIndex + let minIndex: Int + if let _ = self.currentController { + minIndex = 0 + } else { + minIndex = 1 + } + let maxIndex = 2 + + var directions: InteractiveTransitionGestureRecognizerDirections = [] + if currentIndex > minIndex { + directions.insert(.rightCenter) + } + if currentIndex < maxIndex { + directions.insert(.leftCenter) + } + return directions + } + + override func didLoad() { + super.didLoad() + + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in + guard let self else { + return [] + } + return self.panAllowedDirections + }, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0)) + panRecognizer.delegate = self.wrappedGestureRecognizerDelegate + panRecognizer.delaysTouchesBegan = false + panRecognizer.cancelsTouchesInView = true + self.panRecognizer = panRecognizer + self.view.addGestureRecognizer(panRecognizer) + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer { + return false + } + if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { + return true + } + return false + } + + private var panTransitionFraction: CGFloat = 0.0 + private var panCurrentAllowedDirections: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter] + + @objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) { + let translation = gestureRecognizer.translation(in: self.view).x + let velocity = gestureRecognizer.velocity(in: self.view).x + + switch gestureRecognizer.state { + case .began, .changed: + if case .began = gestureRecognizer.state { + self.panCurrentAllowedDirections = self.panAllowedDirections + } + + self.panTransitionFraction = -translation / self.view.bounds.width + if !self.panCurrentAllowedDirections.contains(.leftCenter) { + self.panTransitionFraction = min(0.0, self.panTransitionFraction) + } + if !self.panCurrentAllowedDirections.contains(.rightCenter) { + self.panTransitionFraction = max(0.0, self.panTransitionFraction) + } + + self.searchContentNode.transitionFraction = self.panTransitionFraction + + if let (layout, navigationHeight) = self.containerLayout { + let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate) + } + case .ended, .cancelled: + var directionIsToRight: Bool? + if abs(velocity) > 10.0 { + if translation > 0.0 { + if velocity <= 0.0 { + directionIsToRight = nil + } else { + directionIsToRight = true + } + } else { + if velocity >= 0.0 { + directionIsToRight = nil + } else { + directionIsToRight = false + } + } + } else { + if abs(translation) > self.view.bounds.width / 2.0 { + directionIsToRight = translation > self.view.bounds.width / 2.0 + } + } + if !self.panCurrentAllowedDirections.contains(.rightCenter) && directionIsToRight == true { + directionIsToRight = nil + } + if !self.panCurrentAllowedDirections.contains(.leftCenter) && directionIsToRight == false { + directionIsToRight = nil + } + + if let directionIsToRight { + if directionIsToRight { + self.searchContentNode.selectedIndex -= 1 + } else { + self.searchContentNode.selectedIndex += 1 + } + } + + self.panTransitionFraction = 0.0 + self.searchContentNode.transitionFraction = nil + + if let (layout, navigationHeight) = self.containerLayout { + let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + default: + break + } + } + func updateSearchQuery(_ query: String) { self.query = query @@ -258,15 +372,19 @@ final class HashtagSearchControllerNode: ASDisplayNode { insets.top += navigationBarHeight let toolbarHeight: CGFloat = 40.0 - insets.top += toolbarHeight - 4.0 + + if isFirstTime { + self.insertSubnode(self.clippingNode, at: 0) + } + if let controller = self.currentController { transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)) controller.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 79.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) if controller.displayNode.supernode == nil { controller.viewWillAppear(false) - self.insertSubnode(controller.displayNode, at: 0) + self.containerNode.addSubnode(controller.displayNode) controller.viewDidAppear(false) controller.beginMessageSearch(self.query) @@ -274,12 +392,12 @@ final class HashtagSearchControllerNode: ASDisplayNode { } if let controller = self.myController { - transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)) + transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: layout.size.width, y: 0.0), size: layout.size)) controller.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 89.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) if controller.displayNode.supernode == nil { controller.viewWillAppear(false) - self.insertSubnode(controller.displayNode, at: 0) + self.containerNode.addSubnode(controller.displayNode) controller.viewDidAppear(false) controller.beginMessageSearch(self.query) @@ -287,18 +405,23 @@ final class HashtagSearchControllerNode: ASDisplayNode { } if let controller = self.globalController { - transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)) + transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: layout.size.width * 2.0, y: 0.0), size: layout.size)) controller.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 89.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) if controller.displayNode.supernode == nil { controller.viewWillAppear(false) - self.insertSubnode(controller.displayNode, at: 0) + self.containerNode.addSubnode(controller.displayNode) controller.viewDidAppear(false) controller.beginMessageSearch(self.query) } } + transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: .zero, size: layout.size)) + + let containerPosition: CGFloat = -layout.size.width * CGFloat(self.searchContentNode.selectedIndex) - self.panTransitionFraction * layout.size.width + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: containerPosition, y: 0.0), size: CGSize(width: layout.size.width * 3.0, height: layout.size.height))) + let overflowInset: CGFloat = 0.0 let topInset = navigationBarHeight self.shimmerNode.frame = CGRect(origin: CGPoint(x: overflowInset, y: topInset), size: CGSize(width: layout.size.width - overflowInset * 2.0, height: layout.size.height)) @@ -322,7 +445,6 @@ final class HashtagSearchControllerNode: ASDisplayNode { }) } - if !self.hasValidLayout { self.hasValidLayout = true } diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift index 11e88d64c9..be69d7a643 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift @@ -66,7 +66,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { let updateType: ViewUpdateType = .Initial - let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: false, holeLater: false, isLoading: false) + let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: !result.0.completed, holeLater: false, isLoading: false) self.sourceHistoryView = historyView self.updateHistoryView(updateType: updateType) @@ -87,14 +87,14 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { var entries = self.sourceHistoryView?.entries ?? [] entries.sort(by: { $0.message.index < $1.message.index }) - let mergedHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: entries, holeEarlier: false, holeLater: false, isLoading: false) + let mergedHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: entries, holeEarlier: self.sourceHistoryView?.holeEarlier ?? false, holeLater: false, isLoading: false) self.mergedHistoryView = mergedHistoryView self.historyViewStream.putNext((mergedHistoryView, updateType)) } func loadMore() { - guard self.historyViewDisposable == nil, let currentSearchState = self.currentSearchState, let currentHistoryView = self.sourceHistoryView, currentHistoryView.holeEarlier else { + guard self.historyViewDisposable == nil, let currentSearchState = self.currentSearchState, let sourceHistoryView = self.sourceHistoryView, sourceHistoryView.holeEarlier else { return } diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift index e4204dfe3f..ebd74ef10c 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift @@ -35,6 +35,16 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { } } + var transitionFraction: CGFloat? { + didSet { + if self.transitionFraction != oldValue { + if let (size, leftInset, rightInset) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: self.transitionFraction == nil ? .animated(duration: 0.35, curve: .spring) : .immediate) + } + } + } + } + var isSearching: Bool = false { didSet { self.searchBar.activity = self.isSearching @@ -142,7 +152,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { } self.indexUpdated?(index) }, - transitionFraction: 0.0 + transitionFraction: self.transitionFraction )), environment: {}, containerSize: CGSize(width: size.width, height: 44.0) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 38efb68fd6..eff0bce584 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -132,7 +132,7 @@ private final class StarsContextImpl { self._state = nil self._statePromise.set(.single(nil)) - self.load() + self.load(force: true) self.updateDisposable = (account.stateManager.updatedStarsBalance() |> deliverOnMainQueue).startStrict(next: { [weak self] balances in @@ -140,7 +140,7 @@ private final class StarsContextImpl { return } self.updateState(StarsContext.State(flags: [], balance: balance, transactions: state.transactions, canLoadMore: nextOffset != nil, isLoading: false)) - self.load() + self.load(force: true) }) } @@ -150,9 +150,16 @@ private final class StarsContextImpl { self.updateDisposable?.dispose() } - func load() { + private var previousLoadTimestamp: Double? + func load(force: Bool) { assert(Queue.mainQueue().isCurrent()) + let currentTimestamp = CFAbsoluteTimeGetCurrent() + if let previousLoadTimestamp = self.previousLoadTimestamp, currentTimestamp - previousLoadTimestamp < 60 && !force { + return + } + self.previousLoadTimestamp = currentTimestamp + self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, offset: nil) |> deliverOnMainQueue).start(next: { [weak self] status in if let self { @@ -326,9 +333,9 @@ public final class StarsContext { } } - public func load() { + public func load(force: Bool) { self.impl.with { - $0.load() + $0.load(force: force) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index b49a3df5a8..db9deb4d03 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1531,7 +1531,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) var needsShareButton = false - if case .pinnedMessages = item.associatedData.subject { + if incoming, case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind { + needsShareButton = true + } else if case .pinnedMessages = item.associatedData.subject { needsShareButton = true for media in item.message.media { if let _ = media as? TelegramMediaExpiredContent { @@ -5366,6 +5368,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let item = self.item { if item.message.adAttribute != nil { item.controllerInteraction.openNoAdsDemo() + } else if case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind { + item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, item.content.firstMessage.id, NavigateToMessageParams(timestamp: nil, quote: nil, forceNew: true)) } else if case .pinnedMessages = item.associatedData.subject { item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id) } else if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift index c0b6f916e1..5c6d37291b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift @@ -118,6 +118,9 @@ public class ChatMessageShareButton: ASDisplayNode { if hasMore { updatedBottomIconImage = PresentationResourcesChat.chatFreeMoreButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) } + } else if case let .customChatContents(contents) = subject, case .hashTagSearch = contents.kind { + updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) } else if case .pinnedMessages = subject { updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift index a2a67317f9..99a24124f1 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift @@ -14,17 +14,20 @@ final class StarsBalanceComponent: Component { let theme: PresentationTheme let strings: PresentationStrings let count: Int64 + let purchaseAvailable: Bool let buy: () -> Void init( theme: PresentationTheme, strings: PresentationStrings, count: Int64, + purchaseAvailable: Bool, buy: @escaping () -> Void ) { self.theme = theme self.strings = strings self.count = count + self.purchaseAvailable = purchaseAvailable self.buy = buy } @@ -35,6 +38,9 @@ final class StarsBalanceComponent: Component { if lhs.strings !== rhs.strings { return false } + if lhs.purchaseAvailable != rhs.purchaseAvailable { + return false + } if lhs.count != rhs.count { return false } @@ -66,8 +72,7 @@ final class StarsBalanceComponent: Component { self.component = component let sideInset: CGFloat = 16.0 - - let size = CGSize(width: availableSize.width, height: 172.0) + var contentHeight: CGFloat = sideInset var animatedTextItems: [AnimatedTextComponent.Item] = [] animatedTextItems.append(AnimatedTextComponent.Item( @@ -96,13 +101,14 @@ final class StarsBalanceComponent: Component { let spacing: CGFloat = 3.0 let totalWidth = titleSize.width + icon.size.width + spacing let origin = floorToScreenPixels((availableSize.width - totalWidth) / 2.0) - let titleFrame = CGRect(origin: CGPoint(x: origin + icon.size.width + spacing, y: 13.0), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: origin + icon.size.width + spacing, y: contentHeight - 3.0), size: titleSize) titleView.frame = titleFrame - self.icon.frame = CGRect(origin: CGPoint(x: origin, y: 18.0), size: icon.size) + self.icon.frame = CGRect(origin: CGPoint(x: origin, y: contentHeight + 2.0), size: icon.size) } } - + contentHeight += titleSize.height + let subtitleSize = self.subtitle.update( transition: .immediate, component: AnyComponent( @@ -118,35 +124,42 @@ final class StarsBalanceComponent: Component { if subtitleView.superview == nil { self.addSubview(subtitleView) } - let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - subtitleSize.width) / 2.0), y: 70.0), size: subtitleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - subtitleSize.width) / 2.0), y: contentHeight - 4.0), size: subtitleSize) subtitleView.frame = subtitleFrame } + contentHeight += subtitleSize.height - let buttonSize = self.button.update( - transition: .immediate, - component: AnyComponent( - SolidRoundedButtonComponent( - title: component.strings.Stars_Intro_Buy, - theme: SolidRoundedButtonComponent.Theme(theme: component.theme), - height: 50.0, - cornerRadius: 11.0, - action: { [weak self] in - self?.component?.buy() - } - ) - ), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) - ) - if let buttonView = self.button.view { - if buttonView.superview == nil { - self.addSubview(buttonView) + if component.purchaseAvailable { + contentHeight += 12.0 + + let buttonSize = self.button.update( + transition: .immediate, + component: AnyComponent( + SolidRoundedButtonComponent( + title: component.strings.Stars_Intro_Buy, + theme: SolidRoundedButtonComponent.Theme(theme: component.theme), + height: 50.0, + cornerRadius: 11.0, + action: { [weak self] in + self?.component?.buy() + } + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + ) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize) + buttonView.frame = buttonFrame } - let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: size.height - buttonSize.height - sideInset), size: buttonSize) - buttonView.frame = buttonFrame + contentHeight += buttonSize.height } + contentHeight += sideInset - return size + return CGSize(width: availableSize.width, height: contentHeight) } } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift index 98f3ec0fbf..3b4031b2c2 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -520,6 +520,7 @@ private final class AvatarComponent: Component { imageNode = current } else { imageNode = TransformImageNode() + imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] self.addSubview(imageNode.view) self.imageNode = imageNode @@ -528,7 +529,7 @@ private final class AvatarComponent: Component { } imageNode.frame = CGRect(origin: .zero, size: size) - imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets()))() + imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() self.backgroundView.isHidden = true self.iconView.isHidden = true diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index cb03ef8dc6..bcd827e3e3 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -509,6 +509,7 @@ final class StarsTransactionsScreenComponent: Component { contentHeight += descriptionSize.height contentHeight += 29.0 + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) let balanceSize = self.balanceView.update( transition: .immediate, component: AnyComponent(ListSectionComponent( @@ -520,6 +521,7 @@ final class StarsTransactionsScreenComponent: Component { theme: environment.theme, strings: environment.strings, count: self.starsState?.balance ?? 0, + purchaseAvailable: !premiumConfiguration.areStarsDisabled, buy: { [weak self] in guard let self, let component = self.component else { return @@ -739,7 +741,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { }) } - self.starsContext.load() + self.starsContext.load(force: false) Queue.mainQueue().after(0.5, { self.starsContext.loadMore() diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index 1e1ea46f37..703a3bf35b 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -17,6 +17,7 @@ import PremiumStarComponent import ItemListUI import UndoUI import AccountContext +import PresentationDataUtils private final class SheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -390,20 +391,26 @@ private final class SheetContent: CombinedComponent { displaysProgress: state.inProgress, action: { [weak state, weak controller] in state?.buy(requestTopUp: { [weak controller] completion in - let purchaseController = accountContext.sharedContext.makeStarsPurchaseScreen( - context: accountContext, - starsContext: starsContext, - options: state?.options ?? [], - peerId: state?.peer?.id, - requiredStars: invoice.totalAmount, - completion: { [weak starsContext] stars in - starsContext?.add(balance: stars) - Queue.mainQueue().after(0.1) { - completion() + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: accountContext.currentAppConfiguration.with { $0 }) + if !premiumConfiguration.isPremiumDisabled { + let purchaseController = accountContext.sharedContext.makeStarsPurchaseScreen( + context: accountContext, + starsContext: starsContext, + options: state?.options ?? [], + peerId: state?.peer?.id, + requiredStars: invoice.totalAmount, + completion: { [weak starsContext] stars in + starsContext?.add(balance: stars) + Queue.mainQueue().after(0.1) { + completion() + } } - } - ) - controller?.push(purchaseController) + ) + controller?.push(purchaseController) + } else { + let alertController = textAlertController(context: accountContext, title: nil, text: presentationData.strings.Stars_Transfer_Unavailable, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + controller?.present(alertController, in: .window(.root)) + } }, completion: { [weak controller] in let presentationData = accountContext.sharedContext.currentPresentationData.with { $0 } let resultController = UndoOverlayController( @@ -421,7 +428,7 @@ private final class SheetContent: CombinedComponent { controller?.dismissAnimated() - starsContext.load() + starsContext.load(force: true) }) } ), @@ -567,7 +574,7 @@ public final class StarsTransferScreen: ViewControllerComponentContainer { self.navigationPresentation = .flatModal - starsContext.load() + starsContext.load(force: false) } required public init(coder aDecoder: NSCoder) {