From 54489f428aa5b572b235afb82986508000aaaed6 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 24 Oct 2023 11:57:42 +0400 Subject: [PATCH] Various improvements --- .../Sources/SheetComponent.swift | 12 + .../PremiumUI/Sources/GiftOptionItem.swift | 2 +- .../Sources/PremiumBoostScreen.swift | 123 ++++++-- .../Sources/PremiumLimitScreen.swift | 197 ++++++++++--- .../Sources/ReplaceBoostScreen.swift | 17 +- .../Sources/EmojiPickerItem.swift | 2 +- .../Sources/PeerNameColorScreen.swift | 17 +- .../BoostReplaceIcon.imageset/Contents.json | 12 + .../replacedboost_30.pdf | 273 ++++++++++++++++++ .../Sources/UndoOverlayControllerNode.swift | 2 +- 10 files changed, 571 insertions(+), 86 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/replacedboost_30.pdf diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift index 88b6735a03..cb9cb28dbe 100644 --- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -41,6 +41,14 @@ public let sheetComponentTag = GenericComponentViewTag() public final class SheetComponent: Component { public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment) + public class ExternalState { + public fileprivate(set) var contentHeight: CGFloat + + public init() { + self.contentHeight = 0.0 + } + } + public enum BackgroundColor: Equatable { public enum BlurStyle: Equatable { case light @@ -54,17 +62,20 @@ public final class SheetComponent: Component { public let content: AnyComponent public let backgroundColor: BackgroundColor public let followContentSizeChanges: Bool + public let externalState: ExternalState? public let animateOut: ActionSlot> public init( content: AnyComponent, backgroundColor: BackgroundColor, followContentSizeChanges: Bool = false, + externalState: ExternalState? = nil, animateOut: ActionSlot> ) { self.content = content self.backgroundColor = backgroundColor self.followContentSizeChanges = followContentSizeChanges + self.externalState = externalState self.animateOut = animateOut } @@ -327,6 +338,7 @@ public final class SheetComponent: Component { }, containerSize: containerSize ) + component.externalState?.contentHeight = contentSize.height self.ignoreScrolling = true if let contentView = self.contentView.view { diff --git a/submodules/PremiumUI/Sources/GiftOptionItem.swift b/submodules/PremiumUI/Sources/GiftOptionItem.swift index 4f29d66b28..5fefc71ad6 100644 --- a/submodules/PremiumUI/Sources/GiftOptionItem.swift +++ b/submodules/PremiumUI/Sources/GiftOptionItem.swift @@ -591,7 +591,7 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode { let badgeSize = strongSelf.titleBadge.update( transition: .immediate, component: AnyComponent( - BoostIconComponent(text: badge) + BoostIconComponent(hasIcon: true, text: badge) ), environment: {}, containerSize: CGSize(width: params.width, height: 100.0) diff --git a/submodules/PremiumUI/Sources/PremiumBoostScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostScreen.swift index deb9de1c4e..ac46b5f30a 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostScreen.swift @@ -10,6 +10,34 @@ import PresentationDataUtils //TODO:localize +private struct BoostState { + let level: Int32 + let currentLevelBoosts: Int32 + let nextLevelBoosts: Int32? + let boosts: Int32 + + func displayData(peer: EnginePeer, isCurrent: Bool, myBoostCount: Int32, currentMyBoostCount: Int32, replacedBoosts: Int32? = nil) -> (subject: PremiumLimitScreen.Subject, count: Int32) { + var currentLevel = self.level + var nextLevelBoosts = self.nextLevelBoosts + var currentLevelBoosts = self.currentLevelBoosts + var boosts = self.boosts + if let replacedBoosts { + boosts = max(currentLevelBoosts, boosts - replacedBoosts) + } + + if currentMyBoostCount > 0 && self.boosts == currentLevelBoosts { + currentLevel = max(0, currentLevel - 1) + nextLevelBoosts = currentLevelBoosts + currentLevelBoosts = max(0, currentLevelBoosts - 1) + } + + return ( + .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount), + boosts + ) + } +} + public func PremiumBoostScreen( context: AccountContext, contentContext: Any?, @@ -17,6 +45,7 @@ public func PremiumBoostScreen( isCurrent: Bool, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?, + replacedBoosts: (Int32, Int32)? = nil, forceDark: Bool, openPeer: @escaping (EnginePeer) -> Void, presentController: @escaping (ViewController) -> Void, @@ -35,6 +64,7 @@ public func PremiumBoostScreen( let isPremium = accountPeer.isPremium var myBoostCount: Int32 = 0 + var currentMyBoostCount: Int32 = 0 var availableBoosts: [MyBoostStatus.Boost] = [] var occupiedBoosts: [MyBoostStatus.Boost] = [] if let myBoostStatus { @@ -51,25 +81,15 @@ public func PremiumBoostScreen( } } - var currentLevel = Int32(status.level) - var currentLevelBoosts = Int32(status.currentLevelBoosts) - var nextLevelBoosts = status.nextLevelBoosts.flatMap(Int32.init) - - if myBoostCount > 0 && status.boosts == currentLevelBoosts { - currentLevel = max(0, currentLevel - 1) - nextLevelBoosts = currentLevelBoosts - currentLevelBoosts = max(0, currentLevelBoosts - 1) - } - - let subject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount) - let nextSubject = Promise() - nextSubject.set(.single(.storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount + 1))) - - var nextCount = Int32(status.boosts + 1) + let initialState = BoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts)) + let updatedState = Promise() + updatedState.set(.single(BoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts + 1)))) var updateImpl: (() -> Void)? var dismissImpl: (() -> Void)? - let controller = PremiumLimitScreen(context: context, subject: subject, count: Int32(status.boosts), forceDark: forceDark, action: { + + let (initialSubject, initialCount) = initialState.displayData(peer: peer, isCurrent: isCurrent, myBoostCount: myBoostCount, currentMyBoostCount: 0, replacedBoosts: replacedBoosts?.0) + let controller = PremiumLimitScreen(context: context, subject: initialSubject, count: initialCount, forceDark: forceDark, action: { let dismiss = false updateImpl?() return dismiss @@ -78,33 +98,88 @@ public func PremiumBoostScreen( openPeer(peer) }) pushController(controller) + + if let (replacedBoosts, inChannels) = replacedBoosts { + currentMyBoostCount += 1 + let (subject, count) = initialState.displayData(peer: peer, isCurrent: isCurrent, myBoostCount: myBoostCount, currentMyBoostCount: 1, replacedBoosts: nil) + controller.updateSubject(subject, count: count) + + Queue.mainQueue().after(0.3) { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let undoController = UndoOverlayController(presentationData: presentationData, content: .image(image: generateTintedImage(image: UIImage(bundleImageName: "Premium/BoostReplaceIcon"), color: .white)!, title: nil, text: "\(replacedBoosts) boosts are reassigned from \(inChannels) other channel.", round: false, undoText: nil), elevatedLayout: false, position: .bottom, action: { _ in return true }) + controller.present(undoController, in: .current) + } + } controller.disposed = { dismissed() } + var updating = false + let presentationData = context.sharedContext.currentPresentationData.with { $0 } updateImpl = { [weak controller] in + guard !updating else { + return + } if let _ = status.nextLevelBoosts { if let availableBoost = availableBoosts.first { - let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]).startStandalone() - let _ = (nextSubject.get() + currentMyBoostCount += 1 + myBoostCount += 1 + + updating = true + let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]) + |> deliverOnMainQueue).startStandalone(completed: { + updating = false + + updatedState.set(context.engine.peers.getChannelBoostStatus(peerId: peerId) + |> map { status in + if let status { + return BoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts + 1)) + } else { + return nil + } + }) + }) + + let _ = (updatedState.get() |> take(1) - |> deliverOnMainQueue).startStandalone(next: { nextSubject in - controller?.updateSubject(nextSubject, count: nextCount) + |> deliverOnMainQueue).startStandalone(next: { state in + guard let state else { + return + } + let (subject, count) = state.displayData(peer: peer, isCurrent: isCurrent, myBoostCount: myBoostCount, currentMyBoostCount: currentMyBoostCount) + controller?.updateSubject(subject, count: count) }) availableBoosts.removeFirst() - nextCount += 1 } else if !occupiedBoosts.isEmpty, let myBoostStatus { + var dismissReplaceImpl: (() -> Void)? let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in - let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone() + var channelIds = Set() + for boost in myBoostStatus.boosts { + if slots.contains(boost.slot) { + if let peer = boost.peer { + channelIds.insert(peer.id) + } + } + } - let undoController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "\(slots.count) boosts are reassigned from 1 other channel.", timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, action: { _ in return true }) - presentController(undoController) + let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone(completed: { + let _ = combineLatest(queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { boostStatus, myBoostStatus in + dismissReplaceImpl?() + PremiumBoostScreen(context: context, contentContext: contentContext, peerId: peerId, isCurrent: isCurrent, status: boostStatus, myBoostStatus: myBoostStatus, replacedBoosts: (Int32(slots.count), Int32(channelIds.count)), forceDark: forceDark, openPeer: openPeer, presentController: presentController, pushController: pushController, dismissed: dismissed) + }) + }) }) dismissImpl?() pushController(replaceController) + dismissReplaceImpl = { [weak replaceController] in + replaceController?.dismiss(animated: true) + } } else { if isPremium { let controller = textAlertController( diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index ca4d5b5f40..9c2a51d2a5 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -821,7 +821,7 @@ private final class LimitSheetContent: CombinedComponent { let closeButton = Child(Button.self) let title = Child(MultilineTextComponent.self) let text = Child(BalancedTextComponent.self) - let alternateText = Child(BalancedTextComponent.self) + let alternateText = Child(List.self) let limit = Child(PremiumLimitDisplayComponent.self) let linkButton = Child(SolidRoundedButtonComponent.self) let button = Child(SolidRoundedButtonComponent.self) @@ -881,6 +881,9 @@ private final class LimitSheetContent: CombinedComponent { var peerShortcutChild: _UpdatedChildComponent? var useAlternateText = false + var alternateTitle = "" + var alternateBadge: String? + var titleText = strings.Premium_LimitReached var actionButtonText: String? var actionButtonHasGloss = true @@ -1159,7 +1162,7 @@ private final class LimitSheetContent: CombinedComponent { state.myBoostCount = myBoostCount boostUpdated = true } - useAlternateText = (myBoostCount % 2) != 0 + useAlternateText = myBoostCount > 0 iconName = "Premium/Boost" badgeText = "\(component.count)" @@ -1217,8 +1220,10 @@ private final class LimitSheetContent: CombinedComponent { premiumTitle = "" if myBoostCount > 0 { - let prefixString = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peer.compactDisplayTitle).string : strings.ChannelBoost_YouBoostedOtherChannelText - + alternateTitle = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peer.compactDisplayTitle).string : strings.ChannelBoost_YouBoostedOtherChannelText + if myBoostCount > 1 { + alternateBadge = "X\(myBoostCount)" + } let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1) if let _ = remaining { actionButtonText = "Boost Again" @@ -1246,8 +1251,6 @@ private final class LimitSheetContent: CombinedComponent { } else { string = strings.ChannelBoost_BoostedChannelReachedLevel("\(level + 1)", storiesString).string } - - string = "**\(prefixString)**\n\(string)" } let progress: CGFloat @@ -1299,15 +1302,42 @@ private final class LimitSheetContent: CombinedComponent { var alternateTextChild: _UpdatedChildComponent? if useAlternateText { alternateTextChild = alternateText.update( - component: BalancedTextComponent( - text: .markdown(text: string, attributes: markdownAttributes), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.1 + component: List( + [ + AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + BoostedTitleContent(text: NSAttributedString(string: alternateTitle, font: Font.semibold(15.0), textColor: textColor), badge: alternateBadge) + ) + ), + AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + BalancedTextComponent( + text: .markdown(text: string, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.1 + ) + ) + ) + ], + centerAlignment: true ), availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), transition: .immediate ) + +// alternateTextChild = alternateText.update( +// component: BalancedTextComponent( +// text: .markdown(text: string, attributes: markdownAttributes), +// horizontalAlignment: .center, +// maximumNumberOfLines: 0, +// lineSpacing: 0.1 +// ), +// availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), +// transition: .immediate +// ) } else { textChild = text.update( component: BalancedTextComponent( @@ -1417,7 +1447,7 @@ private final class LimitSheetContent: CombinedComponent { ) buttonOffset += 66.0 - let linkFrame = CGRect(origin: CGPoint(x: sideInset, y: textOffset + ceil((textChild?.size ?? .zero).height / 2.0) + 24.0), size: linkButton.size) + let linkFrame = CGRect(origin: CGPoint(x: sideInset, y: textOffset + (textChild?.size ?? .zero).height + 24.0), size: linkButton.size) context.add(linkButton .position(CGPoint(x: linkFrame.midX, y: linkFrame.midY)) ) @@ -1637,6 +1667,8 @@ private final class LimitSheetComponent: CombinedComponent { let sheet = Child(SheetComponent.self) let animateOut = StoredActionSlot(Action.self) + let sheetExternalState = SheetComponent.ExternalState() + return { context in let environment = context.environment[EnvironmentType.self] @@ -1663,6 +1695,7 @@ private final class LimitSheetComponent: CombinedComponent { )), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), followContentSizeChanges: true, + externalState: sheetExternalState, animateOut: animateOut ), environment: { @@ -1695,6 +1728,22 @@ private final class LimitSheetComponent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) ) + if let controller = controller(), !controller.automaticallyControlPresentationContextLayout { + let layout = ContainerViewLayout( + size: context.availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: max(environment.safeInsets.bottom, sheetExternalState.contentHeight), right: 0.0), + safeInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right), + additionalInsets: .zero, + statusBarHeight: environment.statusBarHeight, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ) + controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition) + } + return context.availableSize } } @@ -1734,6 +1783,9 @@ public class PremiumLimitScreen: ViewControllerComponentContainer { }, openPeer: openPeer, openStats: openStats, openGift: openGift), navigationBarAppearance: .none, statusBarStyle: .ignore, theme: forceDark ? .dark : .default) self.navigationPresentation = .flatModal + if case .storiesChannelBoost = subject { + self.automaticallyControlPresentationContextLayout = false + } self.wasDismissed = cancel @@ -1769,13 +1821,25 @@ public class PremiumLimitScreen: ViewControllerComponentContainer { } public func updateSubject(_ subject: Subject, count: Int32) { - let component = LimitSheetComponent(context: self.context, subject: subject, count: count, cancel: {}, action: { - return true - }, openPeer: self.openPeer, openStats: nil, openGift: nil) + let component = LimitSheetComponent( + context: self.context, + subject: subject, + count: count, + cancel: {}, + action: { [weak self] in + return self?.action?() ?? true + }, + openPeer: self.openPeer, + openStats: nil, + openGift: nil + ) self.updateComponent(component: AnyComponent(component), transition: .easeInOut(duration: 0.2)) + self.animateSuccess() + } + + public func animateSuccess() { self.hapticFeedback.impact() - self.view.addSubview(ConfettiView(frame: self.view.bounds)) } } @@ -1814,8 +1878,6 @@ private final class PeerShortcutComponent: Component { private let avatarNode: AvatarNode private let text = ComponentView() - private let badge = ComponentView() - private var component: PeerShortcutComponent? private weak var state: EmptyComponentState? @@ -1869,29 +1931,6 @@ private final class PeerShortcutComponent: Component { view.frame = textFrame } - if let badge = component.badge { - let badgeSize = self.badge.update( - transition: .immediate, - component: AnyComponent( - BoostIconComponent(text: badge) - ), - environment: {}, - containerSize: availableSize - ) - if let view = self.badge.view { - if view.superview == nil { - self.addSubview(view) - } - - let badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width - badgeSize.width / 2.0), y: -5.0), size: badgeSize) - view.frame = badgeFrame - } - } else { - if let view = self.badge.view { - view.removeFromSuperview() - } - } - self.backgroundView.frame = CGRect(origin: .zero, size: size) return size @@ -1908,13 +1947,18 @@ private final class PeerShortcutComponent: Component { } public final class BoostIconComponent: Component { + let hasIcon: Bool let text: String - public init(text: String) { + public init(hasIcon: Bool, text: String) { + self.hasIcon = hasIcon self.text = text } public static func ==(lhs: BoostIconComponent, rhs: BoostIconComponent) -> Bool { + if lhs.hasIcon != rhs.hasIcon { + return false + } if lhs.text != rhs.text { return false } @@ -1961,11 +2005,11 @@ public final class BoostIconComponent: Component { ) let spacing: CGFloat = 2.0 - var totalWidth = textSize.width + spacing + var totalWidth = textSize.width var iconSize = CGSize() - if let icon = self.badgeIcon.image { + if let icon = self.badgeIcon.image, component.hasIcon { iconSize = CGSize(width: icon.size.width * 0.9, height: icon.size.height * 0.9) - totalWidth += icon.size.width + totalWidth += spacing + icon.size.width } let size = CGSize(width: totalWidth + 8.0, height: 19.0) @@ -1973,7 +2017,7 @@ public final class BoostIconComponent: Component { let iconFrame = CGRect(x: floorToScreenPixels((size.width - totalWidth) / 2.0 + 1.0), y: 4.0 + UIScreenPixel, width: iconSize.width, height: iconSize.height) self.badgeIcon.frame = iconFrame - let textFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: 4.0), size: textSize) + let textFrame = CGRect(origin: CGPoint(x: component.hasIcon ? iconFrame.maxX + spacing : 5.0, y: 4.0), size: textSize) if let view = self.badgeText.view { if view.superview == nil { @@ -1997,3 +2041,64 @@ public final class BoostIconComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + +final class BoostedTitleContent: CombinedComponent { + let text: NSAttributedString + let badge: String? + + init( + text: NSAttributedString, + badge: String? + ) { + self.text = text + self.badge = badge + } + + static func ==(lhs: BoostedTitleContent, rhs: BoostedTitleContent) -> Bool { + if lhs.text != rhs.text { + return false + } + if lhs.badge != rhs.badge { + return false + } + return true + } + + static var body: Body { + let text = Child(MultilineTextComponent.self) + let badge = Child(BoostIconComponent.self) + + return { context in + let component = context.component + + let height: CGFloat = 24.0 + var totalWidth: CGFloat = 0.0 + let text = text.update( + component: MultilineTextComponent( + text: .plain(component.text), + horizontalAlignment: .center + ), + availableSize: CGSize(width: context.availableSize.width - 40.0, height: context.availableSize.height), + transition: .immediate + ) + totalWidth += text.size.width + context.add(text + .position(CGPoint(x: text.size.width / 2.0, y: height / 2.0)) + ) + + if let badgeText = component.badge { + let badge = badge.update( + component: BoostIconComponent(hasIcon: false, text: badgeText), + availableSize: CGSize(width: 24.0, height: 24.0), + transition: .immediate + ) + totalWidth += badge.size.width + 4.0 + context.add(badge + .position(CGPoint(x: totalWidth - badge.size.width / 2.0, y: height / 2.0)) + ) + } + + return CGSize(width: totalWidth, height: height) + } + } +} diff --git a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift index 9f27829137..04587ede66 100644 --- a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift +++ b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift @@ -349,7 +349,6 @@ public class ReplaceBoostScreen: ViewController { return } self.controller?.replaceBoosts?(self.selectedSlots) - self.controller?.dismiss(animated: true) } } @@ -369,7 +368,7 @@ public class ReplaceBoostScreen: ViewController { } @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { + if case .ended = recognizer.state, !self.footerView.inProgress { self.controller?.dismiss(animated: true) } } @@ -950,10 +949,12 @@ private final class FooterView: UIView { fatalError("init(coder:) has not been implemented") } - private var currentLayout: (CGSize, UIEdgeInsets)? + fileprivate var inProgress = false + + private var currentLayout: (CGSize, UIEdgeInsets, PresentationTheme, Int32)? func update(size: CGSize, insets: UIEdgeInsets, theme: PresentationTheme, count: Int32) -> CGFloat { let hadLayout = self.currentLayout != nil - self.currentLayout = (size, insets) + self.currentLayout = (size, insets, theme, count) self.backgroundNode.updateColor(color: theme.rootController.tabBar.backgroundColor, transition: .immediate) self.separatorView.backgroundColor = theme.rootController.tabBar.separatorColor @@ -996,11 +997,15 @@ private final class FooterView: UIView { )) ), isEnabled: true, - displaysProgress: false, + displaysProgress: self.inProgress, action: { [weak self] in - guard let self else { + guard let self, !self.inProgress else { return } + self.inProgress = true + if let (size, insets, theme, count) = self.currentLayout { + let _ = self.update(size: size, insets: insets, theme: theme, count: count) + } self.action() } ) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift index ee20fd4d31..78aac7a4d0 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift @@ -109,7 +109,7 @@ final class EmojiPickerItemNode: ListViewItemNode { let insets: UIEdgeInsets let separatorHeight = UIScreenPixel - let contentSize = CGSize(width: params.width, height: 200.0) + let contentSize = CGSize(width: params.width, height: params.availableHeight - 452.0) insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: contentSize.height - 20.0), insets: insets) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift index b752772d10..127ce4b4bc 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift @@ -189,7 +189,7 @@ private func peerNameColorScreenEntries( state: PeerNameColorScreenState, peer: EnginePeer?, isPremium: Bool, - emojiContent: EmojiPagerContentComponent + emojiContent: EmojiPagerContentComponent? ) -> [PeerNameColorScreenEntry] { var entries: [PeerNameColorScreenEntry] = [] @@ -255,8 +255,10 @@ private func peerNameColorScreenEntries( )) entries.append(.colorDescription(presentationData.strings.NameColor_ChatPreview_Description_Account)) - entries.append(.backgroundEmojiHeader(presentationData.strings.NameColor_BackgroundEmoji_Title)) - entries.append(.backgroundEmoji(emojiContent, nameColor.color)) + if let emojiContent { + entries.append(.backgroundEmojiHeader(presentationData.strings.NameColor_BackgroundEmoji_Title)) + entries.append(.backgroundEmoji(emojiContent, nameColor.color)) + } } return entries @@ -316,7 +318,7 @@ public func PeerNameColorScreen( statePromise.get(), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) ) - |> mapToSignal { state, peer in + |> mapToSignal { state, peer -> Signal in var selectedEmojiId: Int64? if let updatedBackgroundEmojiId = state.updatedBackgroundEmojiId { selectedEmojiId = updatedBackgroundEmojiId @@ -351,6 +353,7 @@ public func PeerNameColorScreen( selectedItems: Set(selectedItems), backgroundIconColor: nameColor ) + |> map(Optional.init) } let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData @@ -359,7 +362,7 @@ public func PeerNameColorScreen( statePromise.get(), context.engine.stickers.availableReactions(), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)), - emojiContent + .single(nil) |> then(emojiContent) ) |> deliverOnMainQueue |> map { presentationData, state, availableReactions, peer, emojiContent -> (ItemListControllerState, (ItemListNodeState, Any)) in @@ -475,7 +478,7 @@ public func PeerNameColorScreen( } ) - emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( + emojiContent?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( performItemAction: { _, item, _, _, _, _ in var selectedFileId: Int64? if let fileId = item.itemFile?.fileId.id { @@ -565,7 +568,7 @@ public func PeerNameColorScreen( entries: entries, style: .blocks, footerItem: footerItem, - animateChanges: true + animateChanges: false ) return (controllerState, (listState, arguments)) diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/Contents.json new file mode 100644 index 0000000000..dc0972e257 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "replacedboost_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/replacedboost_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/replacedboost_30.pdf new file mode 100644 index 0000000000..d6cbcced6a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/replacedboost_30.pdf @@ -0,0 +1,273 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.877197 15.157974 cm +0.000000 0.000000 0.000000 scn +5.552186 8.359201 m +5.243867 8.359201 5.008486 8.634665 5.056572 8.939211 c +5.727019 13.185373 l +5.810456 13.713807 5.119398 13.988482 4.817303 13.546960 c +0.088512 6.635651 l +-0.139333 6.302646 0.099122 5.850563 0.502614 5.850563 c +2.581887 5.850563 l +2.890206 5.850563 3.125587 5.575099 3.077501 5.270554 c +2.407054 1.024391 l +2.323617 0.495958 3.014675 0.221282 3.316770 0.662805 c +8.045561 7.574114 l +8.273406 7.907119 8.034951 8.359201 7.631459 8.359201 c +5.552186 8.359201 l +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 16.877197 1.157974 cm +0.000000 0.000000 0.000000 scn +5.552186 8.359201 m +5.243867 8.359201 5.008486 8.634665 5.056572 8.939211 c +5.727019 13.185373 l +5.810456 13.713807 5.119398 13.988482 4.817303 13.546960 c +0.088512 6.635651 l +-0.139333 6.302646 0.099122 5.850563 0.502614 5.850563 c +2.581887 5.850563 l +2.890206 5.850563 3.125587 5.575099 3.077501 5.270554 c +2.407054 1.024391 l +2.323617 0.495958 3.014675 0.221282 3.316770 0.662805 c +8.045561 7.574114 l +8.273406 7.907119 8.034951 8.359201 7.631459 8.359201 c +5.552186 8.359201 l +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 20.000000 15.177917 cm +0.000000 0.000000 0.000000 scn +6.586899 4.235184 m +6.911034 4.559319 6.911034 5.084846 6.586899 5.408981 c +6.262764 5.733116 5.737236 5.733116 5.413101 5.408981 c +6.586899 4.235184 l +h +3.000000 1.822083 m +2.413101 1.235184 l +2.737236 0.911049 3.262764 0.911049 3.586899 1.235184 c +3.000000 1.822083 l +h +0.586899 5.408981 m +0.262763 5.733116 -0.262763 5.733116 -0.586899 5.408981 c +-0.911034 5.084846 -0.911034 4.559319 -0.586899 4.235184 c +0.586899 5.408981 l +h +5.413101 5.408981 m +2.413101 2.408981 l +3.586899 1.235184 l +6.586899 4.235184 l +5.413101 5.408981 l +h +3.586899 2.408981 m +0.586899 5.408981 l +-0.586899 4.235184 l +2.413101 1.235184 l +3.586899 2.408981 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 16.000000 15.339355 cm +0.000000 0.000000 0.000000 scn +0.000000 10.490644 m +-0.458396 10.490644 -0.830000 10.119040 -0.830000 9.660645 c +-0.830000 9.202249 -0.458396 8.830645 0.000000 8.830645 c +0.000000 10.490644 l +h +6.170000 1.660645 m +6.170000 1.202249 6.541604 0.830645 7.000000 0.830645 c +7.458396 0.830645 7.830000 1.202249 7.830000 1.660645 c +6.170000 1.660645 l +h +6.727516 8.295621 m +7.467052 8.672433 l +6.727516 8.295621 l +h +0.000000 8.830645 m +3.000000 8.830645 l +3.000000 10.490644 l +0.000000 10.490644 l +0.000000 8.830645 l +h +6.170000 5.660645 m +6.170000 1.660645 l +7.830000 1.660645 l +7.830000 5.660645 l +6.170000 5.660645 l +h +3.000000 8.830645 m +3.713761 8.830645 4.199165 8.829999 4.574407 8.799340 c +4.939959 8.769474 5.127283 8.715313 5.258164 8.648625 c +6.011788 10.127696 l +5.607891 10.333492 5.177792 10.415573 4.709584 10.453828 c +4.251065 10.491290 3.686370 10.490644 3.000000 10.490644 c +3.000000 8.830645 l +h +7.830000 5.660645 m +7.830000 6.347014 7.830646 6.911709 7.793183 7.370228 c +7.754929 7.838436 7.672848 8.268535 7.467052 8.672433 c +5.987981 7.918809 l +6.054668 7.787928 6.108829 7.600603 6.138696 7.235051 c +6.169354 6.859809 6.170000 6.374406 6.170000 5.660645 c +7.830000 5.660645 l +h +5.258164 8.648625 m +5.572395 8.488517 5.827872 8.233040 5.987981 7.918809 c +7.467052 8.672433 l +7.147793 9.299013 6.638368 9.808438 6.011788 10.127696 c +5.258164 8.648625 l +h +f +n +Q +q +-1.000000 -0.000000 -0.000000 -1.000000 10.000000 14.822083 cm +0.000000 0.000000 0.000000 scn +6.586899 4.235184 m +6.911034 4.559319 6.911034 5.084846 6.586899 5.408981 c +6.262764 5.733116 5.737236 5.733116 5.413101 5.408981 c +6.586899 4.235184 l +h +3.000000 1.822083 m +2.413101 1.235184 l +2.737236 0.911049 3.262764 0.911049 3.586899 1.235184 c +3.000000 1.822083 l +h +0.586899 5.408981 m +0.262763 5.733116 -0.262763 5.733116 -0.586899 5.408981 c +-0.911034 5.084846 -0.911034 4.559319 -0.586899 4.235184 c +0.586899 5.408981 l +h +5.413101 5.408981 m +2.413101 2.408981 l +3.586899 1.235184 l +6.586899 4.235184 l +5.413101 5.408981 l +h +3.586899 2.408981 m +0.586899 5.408981 l +-0.586899 4.235184 l +2.413101 1.235184 l +3.586899 2.408981 l +h +f +n +Q +q +-1.000000 -0.000000 -0.000000 -1.000000 14.000000 13.660645 cm +0.000000 0.000000 0.000000 scn +0.000000 9.490644 m +-0.458396 9.490644 -0.830000 9.119040 -0.830000 8.660645 c +-0.830000 8.202249 -0.458396 7.830645 0.000000 7.830645 c +0.000000 9.490644 l +h +6.170000 1.660645 m +6.170000 1.202248 6.541604 0.830645 7.000000 0.830645 c +7.458396 0.830645 7.830000 1.202248 7.830000 1.660645 c +6.170000 1.660645 l +h +6.727516 7.295621 m +7.467052 7.672433 l +6.727516 7.295621 l +h +0.000000 7.830645 m +3.000000 7.830645 l +3.000000 9.490644 l +0.000000 9.490644 l +0.000000 7.830645 l +h +6.170000 4.660645 m +6.170000 1.660645 l +7.830000 1.660645 l +7.830000 4.660645 l +6.170000 4.660645 l +h +3.000000 7.830645 m +3.713761 7.830645 4.199165 7.829999 4.574407 7.799341 c +4.939959 7.769474 5.127283 7.715313 5.258164 7.648625 c +6.011788 9.127696 l +5.607891 9.333492 5.177792 9.415573 4.709584 9.453828 c +4.251065 9.491290 3.686370 9.490644 3.000000 9.490644 c +3.000000 7.830645 l +h +7.830000 4.660645 m +7.830000 5.347014 7.830646 5.911709 7.793183 6.370228 c +7.754929 6.838436 7.672848 7.268535 7.467052 7.672433 c +5.987981 6.918809 l +6.054668 6.787928 6.108829 6.600603 6.138696 6.235051 c +6.169354 5.859809 6.170000 5.374406 6.170000 4.660645 c +7.830000 4.660645 l +h +5.258164 7.648625 m +5.572395 7.488517 5.827872 7.233039 5.987981 6.918809 c +7.467052 7.672433 l +7.147793 8.299013 6.638368 8.808438 6.011788 9.127696 c +5.258164 7.648625 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 5530 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000005620 00000 n +0000005643 00000 n +0000005816 00000 n +0000005890 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +5949 +%%EOF \ No newline at end of file diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 52b58ab703..592379be07 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -1374,7 +1374,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { 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: undoButtonFrame.minX, height: contentHeight)) + 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)) var textContentHeight = textSize.height var textOffset: CGFloat = 0.0