diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index 6961e9a27f..1052bb5ef1 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -124,13 +124,14 @@ def load_codesigning_data_from_git(working_dir, repo_url, temp_key_path, branch, encrypted_working_dir = working_dir + '/encrypted' if os.path.exists(encrypted_working_dir): + original_working_dir = os.getcwd() + os.chdir(encrypted_working_dir) if always_fetch: - original_working_dir = os.getcwd() - os.chdir(encrypted_working_dir) check_run_system('GIT_SSH_COMMAND="{ssh_command}" git fetch'.format(ssh_command=ssh_command)) - check_run_system('git checkout "{branch}"'.format(branch=branch)) + check_run_system('git checkout "{branch}"'.format(branch=branch)) + if always_fetch: check_run_system('GIT_SSH_COMMAND="{ssh_command}" git pull'.format(ssh_command=ssh_command)) - os.chdir(original_working_dir) + os.chdir(original_working_dir) else: os.makedirs(encrypted_working_dir, exist_ok=True) original_working_dir = os.getcwd() diff --git a/submodules/TelegramCore/Sources/State/MessageReactions.swift b/submodules/TelegramCore/Sources/State/MessageReactions.swift index 55b47ce212..f4e704c406 100644 --- a/submodules/TelegramCore/Sources/State/MessageReactions.swift +++ b/submodules/TelegramCore/Sources/State/MessageReactions.swift @@ -172,7 +172,7 @@ public func updateMessageReactionsInteractively(account: Account, messageIds: [M |> ignoreValues } -public func sendStarsReactionsInteractively(account: Account, messageId: MessageId, count: Int, isAnonymous: Bool) -> Signal { +public func sendStarsReactionsInteractively(account: Account, messageId: MessageId, count: Int, isAnonymous: Bool?) -> Signal { return account.postbox.transaction { transaction -> Void in transaction.setPendingMessageAction(type: .sendStarsReaction, id: messageId, action: SendStarsReactionsAction(randomId: Int64.random(in: Int64.min ... Int64.max))) transaction.updateMessage(messageId, update: { currentMessage in @@ -182,15 +182,28 @@ public func sendStarsReactionsInteractively(account: Account, messageId: Message } var mappedCount = Int32(count) var attributes = currentMessage.attributes + var resolvedIsAnonymous = false + for attribute in attributes { + if let attribute = attribute as? ReactionsMessageAttribute { + if let myReaction = attribute.topPeers.first(where: { $0.isMy }) { + resolvedIsAnonymous = myReaction.isAnonymous + } + } + } loop: for j in 0 ..< attributes.count { if let current = attributes[j] as? PendingStarsReactionsMessageAttribute { mappedCount += current.count + resolvedIsAnonymous = current.isAnonymous attributes.remove(at: j) break loop } } + + if let isAnonymous { + resolvedIsAnonymous = isAnonymous + } - attributes.append(PendingStarsReactionsMessageAttribute(accountPeerId: account.peerId, count: mappedCount, isAnonymous: isAnonymous)) + attributes.append(PendingStarsReactionsMessageAttribute(accountPeerId: account.peerId, count: mappedCount, isAnonymous: resolvedIsAnonymous)) return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) @@ -395,7 +408,6 @@ private func requestSendStarsReaction(postbox: Postbox, network: Network, stateM } |> mapToSignal { result -> Signal in stateManager.starsContext?.add(balance: Int64(-count), addTransaction: false) - //stateManager.starsContext?.load(force: true) return postbox.transaction { transaction -> Void in transaction.setPendingMessageAction(type: .sendStarsReaction, id: messageId, action: UpdateMessageReactionsAction()) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index a85b0c656c..8404b7fa1d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -334,7 +334,7 @@ public extension TelegramEngine { ).startStandalone() } - public func sendStarsReaction(id: EngineMessage.Id, count: Int, isAnonymous: Bool) { + public func sendStarsReaction(id: EngineMessage.Id, count: Int, isAnonymous: Bool?) { let _ = sendStarsReactionsInteractively(account: self.account, messageId: id, count: count, isAnonymous: isAnonymous).startStandalone() } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 4434370ed2..d43450b988 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -842,12 +842,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { case let .peer(peerId): if peerId != item.context.account.peerId { if peerId.isGroupOrChannel && item.message.author != nil { - var isBroadcastChannel = false - if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - } - - if !isBroadcastChannel { + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case let .broadcast(info) = peer.info { + if info.flags.contains(.messagesShouldHaveProfiles) { + hasAvatar = incoming + } + } else { hasAvatar = true } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 6834eb6632..168967f9a0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1550,7 +1550,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } - if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, firstMessage.author?.id != channel.id { + if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info { if info.flags.contains(.messagesShouldHaveProfiles) { var allowAuthor = incoming overrideEffectiveAuthor = true diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index f7519dfeb3..465993c444 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -75,6 +75,13 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs } } + if let channel = lhs.peers[lhs.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info { + if info.flags.contains(.messagesShouldHaveProfiles) { + lhsEffectiveAuthor = lhs.author + rhsEffectiveAuthor = rhs.author + } + } + var sameChat = true if lhs.id.peerId != rhs.id.peerId { sameChat = false @@ -344,7 +351,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible if !hasActionMedia { if !isBroadcastChannel { hasAvatar = true - } else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, message.author?.id != channel.id { + } else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info { if info.flags.contains(.messagesShouldHaveProfiles) { hasAvatar = true effectiveAuthor = message.author diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index df2ae43e32..c63976ce11 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -464,14 +464,15 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { case let .peer(peerId): if !peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { if peerId.isGroupOrChannel && item.message.author != nil { - var isBroadcastChannel = false - if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case let .broadcast(info) = peer.info { + if info.flags.contains(.messagesShouldHaveProfiles) { + hasAvatar = incoming + } + } else { + hasAvatar = true } - if !isBroadcastChannel { - hasAvatar = true - } else if case .customChatContents = item.chatLocation { + if case .customChatContents = item.chatLocation { hasAvatar = false } } diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index 2085326af0..33fed16370 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -227,6 +227,14 @@ private final class BadgeComponent: Component { required init(coder: NSCoder) { preconditionFailure() } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.badgeView.frame.contains(point) { + return self + } else { + return nil + } + } func update(component: BadgeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { if self.component == nil { @@ -471,14 +479,14 @@ private final class PeerComponent: Component { let theme: PresentationTheme let strings: PresentationStrings let peer: EnginePeer? - let count: Int + let count: String init( context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer?, - count: Int + count: String ) { self.context = context self.theme = theme @@ -547,7 +555,7 @@ private final class PeerComponent: Component { transition: .immediate, component: AnyComponent(PeerBadgeComponent( theme: component.theme, - title: "\(component.count)" + title: component.count )), environment: {}, containerSize: CGSize(width: 200.0, height: 200.0) @@ -806,7 +814,7 @@ private final class SliderBackgroundComponent: Component { topBackgroundTextView.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: animateTopTextAdditionalX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.3, damping: 100.0, additive: true) } - topForegroundTextView.isHidden = component.topCutoff == nil + topForegroundTextView.isHidden = component.topCutoff == nil || topLineFrame.maxX + topTextSize.width + 20.0 > availableSize.width topBackgroundTextView.isHidden = topForegroundTextView.isHidden self.topBackgroundLine.isHidden = topX < 10.0 self.topForegroundLine.isHidden = self.topBackgroundLine.isHidden @@ -915,6 +923,83 @@ private final class ChatSendStarsScreenComponent: Component { } } + private struct Amount: Equatable { + private let sliderSteps: [Int] + private let maxRealValue: Int + let maxSliderValue: Int + private let isLogarithmic: Bool + + private(set) var realValue: Int + private(set) var sliderValue: Int + + private static func makeSliderSteps(maxRealValue: Int, isLogarithmic: Bool) -> [Int] { + if isLogarithmic { + var sliderSteps: [Int] = [ 1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 10_000 ] + sliderSteps.removeAll(where: { $0 >= maxRealValue }) + sliderSteps.append(maxRealValue) + return sliderSteps + } else { + return [1, maxRealValue] + } + } + + private static func remapValueToSlider(realValue: Int, maxSliderValue: Int, steps: [Int]) -> Int { + guard realValue >= steps.first!, realValue <= steps.last! else { return 0 } + + for i in 0 ..< steps.count - 1 { + if realValue >= steps[i] && realValue <= steps[i + 1] { + let range = steps[i + 1] - steps[i] + let relativeValue = realValue - steps[i] + let stepFraction = Float(relativeValue) / Float(range) + return Int(Float(i) * Float(maxSliderValue) / Float(steps.count - 1)) + Int(stepFraction * Float(maxSliderValue) / Float(steps.count - 1)) + } + } + return maxSliderValue // Return max slider position if value equals the last step + } + + private static func remapSliderToValue(sliderValue: Int, maxSliderValue: Int, steps: [Int]) -> Int { + guard sliderValue >= 0, sliderValue <= maxSliderValue else { return steps.first! } + + let stepIndex = Int(Float(sliderValue) / Float(maxSliderValue) * Float(steps.count - 1)) + let fraction = Float(sliderValue) / Float(maxSliderValue) * Float(steps.count - 1) - Float(stepIndex) + + if stepIndex >= steps.count - 1 { + return steps.last! + } else { + let range = steps[stepIndex + 1] - steps[stepIndex] + return steps[stepIndex] + Int(fraction * Float(range)) + } + } + + init(realValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { + self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + self.maxRealValue = maxRealValue + self.maxSliderValue = maxSliderValue + self.isLogarithmic = isLogarithmic + + self.realValue = realValue + self.sliderValue = Amount.remapValueToSlider(realValue: self.realValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps) + } + + init(sliderValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { + self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + self.maxRealValue = maxRealValue + self.maxSliderValue = maxSliderValue + self.isLogarithmic = isLogarithmic + + self.sliderValue = sliderValue + self.realValue = Amount.remapSliderToValue(sliderValue: self.sliderValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps) + } + + func withRealValue(_ realValue: Int) -> Amount { + return Amount(realValue: realValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) + } + + func withSliderValue(_ sliderValue: Int) -> Amount { + return Amount(sliderValue: sliderValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) + } + } + final class View: UIView, UIScrollViewDelegate { private let dimView: UIView private let backgroundLayer: SimpleLayer @@ -959,7 +1044,10 @@ private final class ChatSendStarsScreenComponent: Component { private var topOffsetDistance: CGFloat? private var balance: Int64? - private var amount: Int64 = 1 + + private var amount: Amount = Amount(realValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true) + private var didChangeAmount: Bool = false + private var isAnonymous: Bool = false private var cachedStarImage: (UIImage, PresentationTheme)? private var cachedCloseImage: UIImage? @@ -1058,6 +1146,12 @@ private final class ChatSendStarsScreenComponent: Component { return result } + if let badgeView = self.badge.view, badgeView.hitTest(self.convert(point, to: badgeView), with: event) != nil { + if let sliderView = self.slider.view as? SliderComponent.View, let hitTestTarget = sliderView.hitTestTarget { + return hitTestTarget + } + } + let result = super.hitTest(point, with: event) return result } @@ -1135,7 +1229,11 @@ private final class ChatSendStarsScreenComponent: Component { if self.component == nil { self.balance = component.balance - self.amount = 50 + var isLogarithmic = true + if let data = component.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_stars_reaction_logarithmic_scale"] as? Double { + isLogarithmic = Int(value) != 0 + } + self.amount = Amount(realValue: 50, maxRealValue: component.maxAmount, maxSliderValue: 999, isLogarithmic: isLogarithmic) if let myTopPeer = component.myTopPeer { self.isAnonymous = myTopPeer.isAnonymous } @@ -1182,8 +1280,8 @@ private final class ChatSendStarsScreenComponent: Component { let sliderSize = self.slider.update( transition: transition, component: AnyComponent(SliderComponent( - valueCount: component.maxAmount, - value: Int(self.amount), + valueCount: self.amount.maxSliderValue + 1, + value: self.amount.sliderValue, markPositions: false, trackBackgroundColor: .clear, trackForegroundColor: .clear, @@ -1193,7 +1291,9 @@ private final class ChatSendStarsScreenComponent: Component { guard let self, let component = self.component else { return } - self.amount = 1 + Int64(value) + self.amount = self.amount.withSliderValue(value) + self.didChangeAmount = true + self.state?.updated(transition: ComponentTransition(animation: .none).withUserData(IsAdjustingAmountHint())) let sliderValue = Float(value) / Float(component.maxAmount) @@ -1250,7 +1350,7 @@ private final class ChatSendStarsScreenComponent: Component { let sliderFrame = CGRect(origin: CGPoint(x: sliderInset, y: contentHeight + 127.0), size: sliderSize) let sliderBackgroundFrame = CGRect(origin: CGPoint(x: sliderFrame.minX - 8.0, y: sliderFrame.minY + 7.0), size: CGSize(width: sliderFrame.width + 16.0, height: sliderFrame.height - 14.0)) - let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(component.maxAmount - 1) + let progressFraction: CGFloat = CGFloat(self.amount.sliderValue) / CGFloat(self.amount.maxSliderValue) let topOthersCount: Int? = component.topPeers.filter({ !$0.isMy }).max(by: { $0.count < $1.count })?.count var topCount: Int? @@ -1310,7 +1410,7 @@ private final class ChatSendStarsScreenComponent: Component { transition: transition, component: AnyComponent(BadgeComponent( theme: environment.theme, - title: "\(self.amount)", + title: "\(self.amount.realValue)", inertiaDirection: effectiveInertiaDirection )), environment: {}, @@ -1542,15 +1642,24 @@ private final class ChatSendStarsScreenComponent: Component { if let index = mappedTopPeers.firstIndex(where: { $0.isMy }) { mappedTopPeers.remove(at: index) } - var myCount = Int(self.amount) + + var myCount = 0 if let myTopPeer = component.myTopPeer { myCount += myTopPeer.count } - mappedTopPeers.append(ChatSendStarsScreen.TopPeer( - peer: self.isAnonymous ? nil : component.myPeer, - isMy: true, - count: myCount - )) + var myCountAddition = 0 + if self.didChangeAmount { + myCountAddition = Int(self.amount.realValue) + } + myCount += myCountAddition + if myCount != 0 { + mappedTopPeers.append(ChatSendStarsScreen.TopPeer( + randomIndex: -1, + peer: self.isAnonymous ? nil : component.myPeer, + isMy: true, + count: myCount + )) + } mappedTopPeers.sort(by: { $0.count > $1.count }) if mappedTopPeers.count > 3 { mappedTopPeers = Array(mappedTopPeers.prefix(3)) @@ -1578,6 +1687,11 @@ private final class ChatSendStarsScreenComponent: Component { self.topPeerItems[topPeer.id] = itemView } + let itemCountString = "\(topPeer.count)" + /*if topPeer.isMy && myCountAddition != 0 && topPeer.count > myCountAddition { + itemCountString = "\(topPeer.count - myCountAddition) +\(myCountAddition)" + }*/ + let itemSize = itemView.update( transition: .immediate, component: AnyComponent(PlainButtonComponent( @@ -1586,7 +1700,7 @@ private final class ChatSendStarsScreenComponent: Component { theme: environment.theme, strings: environment.strings, peer: topPeer.peer, - count: topPeer.count + count: itemCountString )), effectAlignment: .center, action: { [weak self] in @@ -1747,7 +1861,7 @@ private final class ChatSendStarsScreenComponent: Component { self.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, environment.theme) } - let buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount)").string + let buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount.realValue)").string let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center) if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 { buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) @@ -1778,7 +1892,7 @@ private final class ChatSendStarsScreenComponent: Component { return } - if balance < self.amount { + if balance < self.amount.realValue { let _ = (component.context.engine.payments.starsTopUpOptions() |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak self] options in @@ -1789,7 +1903,7 @@ private final class ChatSendStarsScreenComponent: Component { return } - let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: .transfer(peerId: component.peer.id, requiredStars: self.amount), completion: { result in + let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: .transfer(peerId: component.peer.id, requiredStars: Int64(self.amount.realValue)), completion: { result in let _ = result //TODO:release }) @@ -1805,13 +1919,13 @@ private final class ChatSendStarsScreenComponent: Component { } let isBecomingTop: Bool if let topCount { - isBecomingTop = self.amount > topCount + isBecomingTop = self.amount.realValue > topCount } else { isBecomingTop = true } component.completion( - self.amount, + Int64(self.amount.realValue), self.isAnonymous, isBecomingTop, ChatSendStarsScreen.TransitionOut( @@ -1953,7 +2067,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { fileprivate final class TopPeer: Equatable { enum Id: Hashable { - case anonymous + case anonymous(Int) case my case peer(EnginePeer.Id) } @@ -1964,7 +2078,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { } else if let peer = self.peer { return .peer(peer.id) } else { - return .anonymous + return .anonymous(self.randomIndex) } } @@ -1972,17 +2086,22 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { return self.peer == nil } + let randomIndex: Int let peer: EnginePeer? let isMy: Bool let count: Int - init(peer: EnginePeer?, isMy: Bool, count: Int) { + init(randomIndex: Int, peer: EnginePeer?, isMy: Bool, count: Int) { + self.randomIndex = randomIndex self.peer = peer self.isMy = isMy self.count = count } static func ==(lhs: TopPeer, rhs: TopPeer) -> Bool { + if lhs.randomIndex != rhs.randomIndex { + return false + } if lhs.peer != rhs.peer { return false } @@ -2099,6 +2218,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { return nil } + var nextRandomIndex = 0 return InitialData( peer: peer, myPeer: myPeer, @@ -2107,7 +2227,10 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { currentSentAmount: currentSentAmount, topPeers: topPeers.compactMap { topPeer -> ChatSendStarsScreen.TopPeer? in guard let topPeerId = topPeer.peerId else { + let randomIndex = nextRandomIndex + nextRandomIndex += 1 return ChatSendStarsScreen.TopPeer( + randomIndex: randomIndex, peer: nil, isMy: topPeer.isMy, count: Int(topPeer.count) @@ -2119,7 +2242,10 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { guard let topPeerValue else { return nil } + let randomIndex = nextRandomIndex + nextRandomIndex += 1 return ChatSendStarsScreen.TopPeer( + randomIndex: randomIndex, peer: topPeer.isAnonymous ? nil : topPeerValue, isMy: topPeer.isMy, count: Int(topPeer.count) @@ -2128,6 +2254,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { myTopPeer: myTopPeer.flatMap { topPeer -> ChatSendStarsScreen.TopPeer? in guard let topPeerId = topPeer.peerId else { return ChatSendStarsScreen.TopPeer( + randomIndex: -1, peer: nil, isMy: topPeer.isMy, count: Int(topPeer.count) @@ -2140,6 +2267,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { return nil } return ChatSendStarsScreen.TopPeer( + randomIndex: -1, peer: topPeer.isAnonymous ? nil : topPeerValue, isMy: topPeer.isMy, count: Int(topPeer.count) diff --git a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift index 5c526ae4b7..e6b4756517 100644 --- a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift +++ b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift @@ -70,6 +70,10 @@ public final class SliderComponent: Component { private var component: SliderComponent? private weak var state: EmptyComponentState? + public var hitTestTarget: UIView? { + return self.sliderView + } + override public init(frame: CGRect) { super.init(frame: frame) } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index a37b02902b..472af48ec3 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -387,8 +387,46 @@ extension ChatControllerImpl { } } - self.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: false) - self.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1) + guard let starsContext = self.context.starsContext else { + return + } + let _ = (starsContext.state + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let strongSelf = self, let balance = state?.balance else { + return + } + + if balance < 1 { + controller?.dismiss(completion: { + guard let strongSelf = self else { + return + } + + let _ = (strongSelf.context.engine.payments.starsTopUpOptions() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] options in + guard let strongSelf, let peerId = strongSelf.chatLocation.peerId else { + return + } + guard let starsContext = strongSelf.context.starsContext else { + return + } + + let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .transfer(peerId: peerId, requiredStars: 1), completion: { result in + let _ = result + //TODO:release + }) + strongSelf.push(purchaseScreen) + }) + }) + + return + } + + strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: nil) + strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1) + }) } else { let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index e6bf3d31f7..4c3824f63d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1729,7 +1729,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: false) + strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: nil) strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1) }) } else { diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 75770ad4aa..4705c4afc6 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -29,6 +29,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private let elevatedLayout: Bool private let placementPosition: UndoOverlayController.Position private var statusNode: RadialStatusNode? + private var didStartStatusNode: Bool = false private let timerTextNode: ImmediateTextNode private let avatarNode: AvatarNode? private let iconNode: ASImageNode? @@ -67,6 +68,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private var isTimeoutDisabled: Bool = false private var originalRemainingSeconds: Double private var remainingSeconds: Double + private let undoTextColor: UIColor private var timer: SwiftSignalKit.Timer? private var validLayout: ContainerViewLayout? @@ -413,8 +415,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.animatedTextItems = textItems displayUndo = true - self.originalRemainingSeconds = 4.5 + self.originalRemainingSeconds = 4.9 isUserInteractionEnabled = true + + self.statusNode = RadialStatusNode(backgroundNodeColor: .clear) case let .messagesUnpinned(title, text, undo, isHidden): self.avatarNode = nil self.iconNode = nil @@ -1246,6 +1250,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } self.remainingSeconds = self.originalRemainingSeconds + self.undoTextColor = undoTextColor self.undoButtonTextNode = ImmediateTextNode() self.undoButtonTextNode.displaysAsynchronously = false @@ -1271,7 +1276,15 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { switch content { case .removedChat: self.panelWrapperNode.addSubnode(self.timerTextNode) - case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .starsSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .premiumPaywall, .peers, .messageTagged: + case .starsSent: + self.panelWrapperNode.addSubnode(self.timerTextNode) + + if self.textNode.tapAttributeAction != nil || displayUndo { + self.isUserInteractionEnabled = true + } else { + self.isUserInteractionEnabled = false + } + case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .premiumPaywall, .peers, .messageTagged: if self.textNode.tapAttributeAction != nil || displayUndo { self.isUserInteractionEnabled = true } else { @@ -1420,7 +1433,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let _ = self.action(.commit) self.dismiss() } else { - if Int(self.remainingSeconds) != previousRemainingSeconds || (self.timerTextNode.attributedText?.string ?? "").isEmpty { + let remainingSecondsString = "\(Int(self.remainingSeconds))" + if Int(self.remainingSeconds) != previousRemainingSeconds || self.timerTextNode.attributedText?.string != remainingSecondsString { if !self.timerTextNode.bounds.size.width.isZero, let snapshot = self.timerTextNode.view.snapshotContentTree() { self.panelNode.view.insertSubview(snapshot, aboveSubview: self.timerTextNode.view) snapshot.frame = self.timerTextNode.frame @@ -1431,7 +1445,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { snapshot?.removeFromSuperview() }) } - self.timerTextNode.attributedText = NSAttributedString(string: "\(Int(self.remainingSeconds))", font: Font.regular(16.0), textColor: .white) + let timerColor: UIColor + if case .starsSent = self.content { + timerColor = self.undoTextColor + } else { + timerColor = .white + } + self.timerTextNode.attributedText = NSAttributedString(string: remainingSecondsString, font: Font.regular(16.0), textColor: timerColor) if let validLayout = self.validLayout { self.containerLayoutUpdated(layout: validLayout, transition: .immediate) } @@ -1450,6 +1470,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.timer?.invalidate() self.timer = nil self.remainingSeconds = self.originalRemainingSeconds + self.didStartStatusNode = false self.checkTimer() } @@ -1508,7 +1529,6 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - let firstLayout = self.validLayout == nil self.validLayout = layout var preferredSize: CGSize? @@ -1630,10 +1650,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { transition.updateFrame(node: self.panelWrapperNode, frame: panelWrapperFrame) self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight) - let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - leftMargin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize) + var buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - leftMargin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize) + var undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - leftMargin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + leftMargin, height: contentHeight)) + if case .starsSent = self.content { + let buttonOffset: CGFloat = -34.0 + undoButtonFrame.origin.x += buttonOffset + buttonTextFrame.origin.x += buttonOffset + } transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame) - - let undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - leftMargin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + leftMargin, height: contentHeight)) self.undoButtonNode.frame = undoButtonFrame self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.undoButtonNode.supernode == nil ? panelFrame.width : undoButtonFrame.minX, height: contentHeight)) @@ -1728,13 +1752,28 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } let timerTextSize = self.timerTextNode.updateLayout(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - timerTextSize.width) / 2.0), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize)) + if case .starsSent = self.content { + transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset + floor((rightInset - timerTextSize.width) * 0.5) - 46.0)), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize)) + } else { + transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - timerTextSize.width) / 2.0), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize)) + } if let statusNode = self.statusNode { let statusSize: CGFloat = 30.0 - transition.updateFrame(node: statusNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize))) - if firstLayout { - statusNode.transitionToState(.secretTimeout(color: .white, icon: .none, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {}) + var statusFrame = CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize)) + if case .starsSent = self.content { + statusFrame.origin.x = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - statusSize - 23.0 + } + transition.updateFrame(node: statusNode, frame: statusFrame) + if !self.didStartStatusNode { + let statusColor: UIColor + if case .starsSent = self.content { + statusColor = self.undoTextColor + } else { + statusColor = .white + } + statusNode.transitionToState(.secretTimeout(color: statusColor, icon: .none, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {}) + self.didStartStatusNode = true } } diff --git a/versions.json b/versions.json index 0687306f8e..62225777ec 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "11.0", + "app": "11.0.1", "xcode": "15.2", "bazel": "7.1.1", "macos": "13.0"