diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 7de9fbddd0..c0386381a0 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13587,3 +13587,17 @@ Sorry for the inconvenience."; "VideoChat.IncomingVideoQuality.Title" = "Receive Video Quality"; "ChatList.EmptyResult.SearchInAll" = "Search in All Messages"; + +"Gift.Options.GiftSelf.Title" = "Buy a Gift"; +"Gift.Options.GiftSelf.Text" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later."; + +"Gift.SendSelf.Title" = "Gift to Myself"; +"Gift.SendSelf.HideMyName" = "Hide My Name"; +"Gift.SendSelf.HideMyName.Info" = "Hide my name and message from visitors to my profile."; + +"Notification.StarGift.Self.Title" = "Saved Gift"; +"Notification.StarGift.Self.Bought" = "You bought a gift for %@"; +"Notification.StarGift.Self.Bought" = "You can display this gift on your page or turn it into a unique collectible and send to others."; + +"Gift.View.Self.Title" = "Saved Gift"; +"Gift.View.Self.Description" = "You can display this gift on your page or turn it into a unique collectible and send to others."; diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 9991b69e9e..1e6b12fa59 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1887,6 +1887,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { var query: String? } let previousRecentlySearchedPeersState = Atomic(value: nil) + let hadAnySearchMessages = Atomic(value: false) let foundItems: Signal<([ChatListSearchEntry], Bool)?, NoError> = combineLatest(queue: .mainQueue(), searchQuery, searchOptions, self.searchScopePromise.get(), downloadItems) |> mapToSignal { [weak self] query, options, searchScope, downloadItems -> Signal<([ChatListSearchEntry], Bool)?, NoError> in @@ -2992,9 +2993,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } + let hadAnySearchMessagesBefore = hadAnySearchMessages.with { $0 } var existingMessageIds = Set() - if foundRemoteMessages.1 && searchScope != .everywhere { - for i in 0 ..< 5 { + if foundRemoteMessages.1 && (searchScope != .everywhere || hadAnySearchMessagesBefore) { + for i in 0 ..< 6 { entries.append(.messagePlaceholder(Int32(i), presentationData, searchScope)) index += 1 } @@ -3029,7 +3031,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { index += 1 } } - if !hasAnyMessages { + + if hasAnyMessages { + let _ = hadAnySearchMessages.swap(true) + } else { switch searchScope { case .everywhere: break diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 295f34f521..953fc62c63 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -4721,7 +4721,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let dateFrame = strongSelf.dateNode.frame shapes.append(.roundedRectLine(startPoint: CGPoint(x: dateFrame.maxX - dateLineWidth, y: dateFrame.minY + 3.0), width: dateLineWidth, diameter: lineDiameter)) - shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerNode.frame.size) + shimmerNode.update(backgroundColor: item.presentationData.theme.list.plainBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerNode.frame.size) } else if let shimmerNode = strongSelf.placeholderNode { strongSelf.placeholderNode = nil shimmerNode.removeFromSupernode() diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 08827059e0..b18395f5ec 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -1441,6 +1441,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-242427324] = { return Api.upload.File.parse_fileCdnRedirect($0) } dict[568808380] = { return Api.upload.WebFile.parse_webFile($0) } dict[997004590] = { return Api.users.UserFull.parse_userFull($0) } + dict[1658259128] = { return Api.users.Users.parse_users($0) } + dict[828000628] = { return Api.users.Users.parse_usersSlice($0) } return dict }() @@ -2550,6 +2552,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.users.UserFull: _1.serialize(buffer, boxed) + case let _1 as Api.users.Users: + _1.serialize(buffer, boxed) default: break } diff --git a/submodules/TelegramApi/Sources/Api37.swift b/submodules/TelegramApi/Sources/Api37.swift index 436562b8b4..a5d3434758 100644 --- a/submodules/TelegramApi/Sources/Api37.swift +++ b/submodules/TelegramApi/Sources/Api37.swift @@ -56,3 +56,75 @@ public extension Api.users { } } +public extension Api.users { + enum Users: TypeConstructorDescription { + case users(users: [Api.User]) + case usersSlice(count: Int32, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .users(let users): + if boxed { + buffer.appendInt32(1658259128) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .usersSlice(let count, let users): + if boxed { + buffer.appendInt32(828000628) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .users(let users): + return ("users", [("users", users as Any)]) + case .usersSlice(let count, let users): + return ("usersSlice", [("count", count as Any), ("users", users as Any)]) + } + } + + public static func parse_users(_ reader: BufferReader) -> Users? { + var _1: [Api.User]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.users.Users.users(users: _1!) + } + else { + return nil + } + } + public static func parse_usersSlice(_ reader: BufferReader) -> Users? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.users.Users.usersSlice(count: _1!, users: _2!) + } + else { + return nil + } + } + + } +} diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index 52fe9e9c13..9df97e6f8b 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -2382,6 +2382,22 @@ public extension Api.functions.bots { }) } } +public extension Api.functions.bots { + static func getBotRecommendations(flags: Int32, bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(676707937) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + return (FunctionDescription(name: "bots.getBotRecommendations", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.Users? in + let reader = BufferReader(buffer) + var result: Api.users.Users? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.users.Users + } + return result + }) + } +} public extension Api.functions.bots { static func getPopularAppBots(offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramUI/Components/ButtonComponent/BUILD b/submodules/TelegramUI/Components/ButtonComponent/BUILD index 8d1557f50b..eeb8fe1cab 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/ButtonComponent/BUILD @@ -14,6 +14,7 @@ swift_library( "//submodules/ComponentFlow", "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/ActivityIndicator", + "//submodules/ShimmerEffect", "//submodules/Components/BundleIconComponent", ], visibility = [ diff --git a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift index e95ff7b5aa..9d14e292aa 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift +++ b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift @@ -5,6 +5,7 @@ import ComponentFlow import AnimatedTextComponent import ActivityIndicator import BundleIconComponent +import ShimmerEffect public final class ButtonBadgeComponent: Component { let fillColor: UIColor @@ -341,17 +342,20 @@ public final class ButtonComponent: Component { public var foreground: UIColor public var pressedColor: UIColor public var cornerRadius: CGFloat + public var isShimmering: Bool public init( color: UIColor, foreground: UIColor, pressedColor: UIColor, - cornerRadius: CGFloat = 10.0 + cornerRadius: CGFloat = 10.0, + isShimmering: Bool = false ) { self.color = color self.foreground = foreground self.pressedColor = pressedColor self.cornerRadius = cornerRadius + self.isShimmering = isShimmering } } @@ -416,6 +420,7 @@ public final class ButtonComponent: Component { private var component: ButtonComponent? private weak var componentState: EmptyComponentState? + private var shimmeringView: ButtonShimmeringView? private var contentItem: ContentItem? private var activityIndicator: ActivityIndicator? @@ -468,7 +473,7 @@ public final class ButtonComponent: Component { } else if !component.isEnabled && component.tintWhenDisabled { contentAlpha = 0.7 } - + var previousContentItem: ContentItem? let contentItem: ContentItem var contentItemTransition = transition @@ -544,6 +549,25 @@ public final class ButtonComponent: Component { } } + if component.background.isShimmering { + let shimmeringView: ButtonShimmeringView + var shimmeringTransition = transition + if let current = self.shimmeringView { + shimmeringView = current + } else { + shimmeringTransition = .immediate + shimmeringView = ButtonShimmeringView(frame: .zero) + self.insertSubview(shimmeringView, at: 0) + } + shimmeringView.update(size: availableSize, color: component.background.color, cornerRadius: component.background.cornerRadius, transition: shimmeringTransition) + shimmeringTransition.setFrame(view: shimmeringView, frame: CGRect(origin: .zero, size: availableSize)) + } else if let shimmeringView = self.shimmeringView { + self.shimmeringView = nil + shimmeringView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { _ in + shimmeringView.removeFromSuperview() + }) + } + return availableSize } } @@ -556,3 +580,61 @@ public final class ButtonComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + +private class ButtonShimmeringView: UIView { + private var shimmerView = ShimmerEffectForegroundView() + private var borderView = UIView() + private var borderMaskView = UIView() + private var borderShimmerView = ShimmerEffectForegroundView() + + override init(frame: CGRect) { + self.borderView.isUserInteractionEnabled = false + + self.borderMaskView.layer.borderWidth = 1.0 + UIScreenPixel + self.borderMaskView.layer.borderColor = UIColor.white.cgColor + self.borderView.mask = self.borderMaskView + + self.borderView.addSubview(self.borderShimmerView) + + super.init(frame: frame) + + self.addSubview(self.shimmerView) + self.addSubview(self.borderView) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + func update(size: CGSize, color: UIColor, cornerRadius: CGFloat, transition: ComponentTransition) { + let alpha: CGFloat + let borderAlpha: CGFloat + let compositingFilter: String? + if color.lightness > 0.5 { + alpha = 0.5 + borderAlpha = 0.75 + compositingFilter = "overlayBlendMode" + } else { + alpha = 0.2 + borderAlpha = 0.3 + compositingFilter = nil + } + + self.shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, globalTimeOffset: false, duration: 4.0, horizontal: true) + self.shimmerView.layer.compositingFilter = compositingFilter + + self.borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, globalTimeOffset: false, duration: 4.0, horizontal: true) + self.borderShimmerView.layer.compositingFilter = compositingFilter + + self.borderMaskView.layer.cornerRadius = cornerRadius + + let bounds = CGRect(origin: .zero, size: size) + transition.setFrame(view: self.shimmerView, frame: bounds) + transition.setFrame(view: self.borderView, frame: bounds) + transition.setFrame(view: self.borderMaskView, frame: bounds) + transition.setFrame(view: self.borderShimmerView, frame: bounds) + + self.shimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: size.width * 4.0, y: 0.0), size: size), within: CGSize(width: size.width * 9.0, height: size.height)) + self.borderShimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: size.width * 4.0, y: 0.0), size: size), within: CGSize(width: size.width * 9.0, height: size.height)) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 6b41e0602f..af9c1913cf 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -480,6 +480,8 @@ final class GiftOptionsScreenComponent: Component { let _ = bottomContentInset let _ = sectionSpacing + let isSelfGift = component.peerId == component.context.account.peerId + var contentHeight: CGFloat = 0.0 contentHeight += environment.navigationHeight - 56.0 + 188.0 @@ -614,7 +616,7 @@ final class GiftOptionsScreenComponent: Component { let premiumTitleSize = self.premiumTitle.update( transition: transition, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: strings.Gift_Options_Premium_Title, font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)), + text: .plain(NSAttributedString(string: isSelfGift ? strings.Gift_Options_GiftSelf_Title : strings.Gift_Options_Premium_Title, font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)), horizontalAlignment: .center )), environment: {}, @@ -635,7 +637,7 @@ final class GiftOptionsScreenComponent: Component { }) let peerName = state.peer?.compactDisplayTitle ?? "" - let premiumDescriptionString = parseMarkdownIntoAttributedString(strings.Gift_Options_Premium_Text(peerName).string, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString + let premiumDescriptionString = parseMarkdownIntoAttributedString(isSelfGift ? strings.Gift_Options_GiftSelf_Text : strings.Gift_Options_Premium_Text(peerName).string, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString if let range = premiumDescriptionString.string.range(of: ">"), let chevronImage = self.chevronImage?.0 { premiumDescriptionString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: premiumDescriptionString.string)) } @@ -689,188 +691,191 @@ final class GiftOptionsScreenComponent: Component { let optionSpacing: CGFloat = 10.0 let optionWidth = (availableSize.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0 - if let premiumProducts = state.premiumProducts { - let premiumOptionSize = CGSize(width: optionWidth, height: 178.0) - - var validIds: [AnyHashable] = [] - var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: premiumOptionSize) - for product in premiumProducts { - let itemId = AnyHashable(product.id) - validIds.append(itemId) + if isSelfGift { + contentHeight += 6.0 + } else { + if let premiumProducts = state.premiumProducts { + let premiumOptionSize = CGSize(width: optionWidth, height: 178.0) - var itemTransition = transition - let visibleItem: ComponentView - if let current = self.premiumItems[itemId] { - visibleItem = current - } else { - visibleItem = ComponentView() - if !transition.animation.isImmediate { - itemTransition = .immediate - } - self.premiumItems[itemId] = visibleItem - } - - let title: String - switch product.months { - case 6: - title = strings.Gift_Options_Premium_Months(6) - case 12: - title = strings.Gift_Options_Premium_Years(1) - default: - title = strings.Gift_Options_Premium_Months(3) - } - - let _ = visibleItem.update( - transition: itemTransition, - component: AnyComponent( - PlainButtonComponent( - content: AnyComponent( - GiftItemComponent( - context: component.context, - theme: theme, - peer: nil, - subject: .premium(months: product.months, price: product.price), - title: title, - subtitle: strings.Gift_Options_Premium_Premium, - ribbon: product.discount.flatMap { - GiftItemComponent.Ribbon( - text: "-\($0)%", - color: .red - ) - }, - isLoading: self.inProgressPremiumGift == product.id - ) - ), - effectAlignment: .center, - action: { [weak self] in - if let self, let component = self.component { - if let controller = controller() as? GiftOptionsScreen { - let mainController: ViewController - if let parentController = controller.parentController() { - mainController = parentController - } else { - mainController = controller - } - let giftController = GiftSetupScreen( - context: component.context, - peerId: component.peerId, - subject: .premium(product), - completion: component.completion - ) - mainController.push(giftController) - } - } - }, - animateAlpha: false - ) - ), - environment: {}, - containerSize: premiumOptionSize - ) - if let itemView = visibleItem.view { - if itemView.superview == nil { - self.scrollView.addSubview(itemView) - if !transition.animation.isImmediate { - transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) - } - } - itemTransition.setFrame(view: itemView, frame: itemFrame) - } - itemFrame.origin.x += itemFrame.width + optionSpacing - if itemFrame.maxX > availableSize.width { - itemFrame.origin.x = sideInset - itemFrame.origin.y += premiumOptionSize.height + optionSpacing - } - } - - var removeIds: [AnyHashable] = [] - for (id, item) in self.premiumItems { - if !validIds.contains(id) { - removeIds.append(id) - if let itemView = item.view { - if !transition.animation.isImmediate { - itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in - itemView.removeFromSuperview() - }) - } else { - itemView.removeFromSuperview() - } - } - } - } - for id in removeIds { - self.premiumItems.removeValue(forKey: id) - } - - contentHeight += ceil(CGFloat(premiumProducts.count) / 3.0) * premiumOptionSize.height - contentHeight += 66.0 - } - - - let starsTitleSize = self.starsTitle.update( - transition: transition, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: strings.Gift_Options_Gift_Title, font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)), - horizontalAlignment: .center - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 100.0) - ) - if let starsTitleView = self.starsTitle.view { - if starsTitleView.superview == nil { - self.addSubview(starsTitleView) - } - transition.setBounds(view: starsTitleView, bounds: CGRect(origin: .zero, size: starsTitleSize)) - } - - let starsDescriptionString = parseMarkdownIntoAttributedString(strings.Gift_Options_Gift_Text(peerName).string, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString - if let range = starsDescriptionString.string.range(of: ">"), let chevronImage = self.chevronImage?.0 { - starsDescriptionString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: starsDescriptionString.string)) - } - let starsDescriptionSize = self.starsDescription.update( - transition: transition, - component: AnyComponent(BalancedTextComponent( - text: .plain(starsDescriptionString), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.2, - highlightColor: accentColor.withAlphaComponent(0.1), - highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0), - highlightAction: { attributes in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { - return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + var validIds: [AnyHashable] = [] + var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: premiumOptionSize) + for product in premiumProducts { + let itemId = AnyHashable(product.id) + validIds.append(itemId) + + var itemTransition = transition + let visibleItem: ComponentView + if let current = self.premiumItems[itemId] { + visibleItem = current } else { - return nil - } - }, - tapAction: { [weak self] _, _ in - guard let self, let component = self.component, let environment = self.environment else { - return - } - let introController = component.context.sharedContext.makeStarsIntroScreen(context: component.context) - if let controller = environment.controller() as? GiftOptionsScreen { - let mainController: ViewController - if let parentController = controller.parentController() { - mainController = parentController - } else { - mainController = controller + visibleItem = ComponentView() + if !transition.animation.isImmediate { + itemTransition = .immediate } - mainController.push(introController) + self.premiumItems[itemId] = visibleItem + } + + let title: String + switch product.months { + case 6: + title = strings.Gift_Options_Premium_Months(6) + case 12: + title = strings.Gift_Options_Premium_Years(1) + default: + title = strings.Gift_Options_Premium_Months(3) + } + + let _ = visibleItem.update( + transition: itemTransition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + GiftItemComponent( + context: component.context, + theme: theme, + peer: nil, + subject: .premium(months: product.months, price: product.price), + title: title, + subtitle: strings.Gift_Options_Premium_Premium, + ribbon: product.discount.flatMap { + GiftItemComponent.Ribbon( + text: "-\($0)%", + color: .red + ) + }, + isLoading: self.inProgressPremiumGift == product.id + ) + ), + effectAlignment: .center, + action: { [weak self] in + if let self, let component = self.component { + if let controller = controller() as? GiftOptionsScreen { + let mainController: ViewController + if let parentController = controller.parentController() { + mainController = parentController + } else { + mainController = controller + } + let giftController = GiftSetupScreen( + context: component.context, + peerId: component.peerId, + subject: .premium(product), + completion: component.completion + ) + mainController.push(giftController) + } + } + }, + animateAlpha: false + ) + ), + environment: {}, + containerSize: premiumOptionSize + ) + if let itemView = visibleItem.view { + if itemView.superview == nil { + self.scrollView.addSubview(itemView) + if !transition.animation.isImmediate { + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + } + } + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + itemFrame.origin.x += itemFrame.width + optionSpacing + if itemFrame.maxX > availableSize.width { + itemFrame.origin.x = sideInset + itemFrame.origin.y += premiumOptionSize.height + optionSpacing } } - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 1000.0) - ) - let starsDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - starsDescriptionSize.width) / 2.0), y: contentHeight), size: starsDescriptionSize) - if let starsDescriptionView = self.starsDescription.view { - if starsDescriptionView.superview == nil { - self.scrollView.addSubview(starsDescriptionView) + + var removeIds: [AnyHashable] = [] + for (id, item) in self.premiumItems { + if !validIds.contains(id) { + removeIds.append(id) + if let itemView = item.view { + if !transition.animation.isImmediate { + itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + itemView.removeFromSuperview() + }) + } else { + itemView.removeFromSuperview() + } + } + } + } + for id in removeIds { + self.premiumItems.removeValue(forKey: id) + } + + contentHeight += ceil(CGFloat(premiumProducts.count) / 3.0) * premiumOptionSize.height + contentHeight += 66.0 } - transition.setFrame(view: starsDescriptionView, frame: starsDescriptionFrame) + + let starsTitleSize = self.starsTitle.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: strings.Gift_Options_Gift_Title, font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + if let starsTitleView = self.starsTitle.view { + if starsTitleView.superview == nil { + self.addSubview(starsTitleView) + } + transition.setBounds(view: starsTitleView, bounds: CGRect(origin: .zero, size: starsTitleSize)) + } + + let starsDescriptionString = parseMarkdownIntoAttributedString(strings.Gift_Options_Gift_Text(peerName).string, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString + if let range = starsDescriptionString.string.range(of: ">"), let chevronImage = self.chevronImage?.0 { + starsDescriptionString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: starsDescriptionString.string)) + } + let starsDescriptionSize = self.starsDescription.update( + transition: transition, + component: AnyComponent(BalancedTextComponent( + text: .plain(starsDescriptionString), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: accentColor.withAlphaComponent(0.1), + highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { [weak self] _, _ in + guard let self, let component = self.component, let environment = self.environment else { + return + } + let introController = component.context.sharedContext.makeStarsIntroScreen(context: component.context) + if let controller = environment.controller() as? GiftOptionsScreen { + let mainController: ViewController + if let parentController = controller.parentController() { + mainController = parentController + } else { + mainController = controller + } + mainController.push(introController) + } + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 1000.0) + ) + let starsDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - starsDescriptionSize.width) / 2.0), y: contentHeight), size: starsDescriptionSize) + if let starsDescriptionView = self.starsDescription.view { + if starsDescriptionView.superview == nil { + self.scrollView.addSubview(starsDescriptionView) + } + transition.setFrame(view: starsDescriptionView, frame: starsDescriptionFrame) + } + contentHeight += starsDescriptionSize.height + contentHeight += 16.0 } - contentHeight += starsDescriptionSize.height - contentHeight += 16.0 var tabSelectorItems: [TabSelectorComponent.Item] = [] tabSelectorItems.append(TabSelectorComponent.Item( @@ -1022,41 +1027,43 @@ final class GiftOptionsScreenComponent: Component { } self.peer = peer - if availableProducts.isEmpty { - var premiumProducts: [PremiumGiftProduct] = [] - for option in premiumOptions { - premiumProducts.append( - PremiumGiftProduct( - giftOption: CachedPremiumGiftOption( - months: option.months, - currency: option.currency, - amount: option.amount, - botUrl: "", - storeProductId: option.storeProductId - ), - storeProduct: nil, - discount: nil + if peerId != context.account.peerId { + if availableProducts.isEmpty { + var premiumProducts: [PremiumGiftProduct] = [] + for option in premiumOptions { + premiumProducts.append( + PremiumGiftProduct( + giftOption: CachedPremiumGiftOption( + months: option.months, + currency: option.currency, + amount: option.amount, + botUrl: "", + storeProductId: option.storeProductId + ), + storeProduct: nil, + discount: nil + ) ) - ) - } - self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months }) - } else { - let shortestOptionPrice: (Int64, NSDecimalNumber) - if let product = availableProducts.first(where: { $0.id.hasSuffix(".monthly") }) { - shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue) - } else { - shortestOptionPrice = (1, NSDecimalNumber(decimal: 1)) - } - - var premiumProducts: [PremiumGiftProduct] = [] - for option in premiumOptions { - if let product = availableProducts.first(where: { $0.id == option.storeProductId }), !product.isSubscription { - let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(option.months) / Float(shortestOptionPrice.0) - let discountValue = Int(round((1.0 - fraction) * 20.0) * 5.0) - premiumProducts.append(PremiumGiftProduct(giftOption: option, storeProduct: product, discount: discountValue > 0 ? discountValue : nil)) } + self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months }) + } else { + let shortestOptionPrice: (Int64, NSDecimalNumber) + if let product = availableProducts.first(where: { $0.id.hasSuffix(".monthly") }) { + shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue) + } else { + shortestOptionPrice = (1, NSDecimalNumber(decimal: 1)) + } + + var premiumProducts: [PremiumGiftProduct] = [] + for option in premiumOptions { + if let product = availableProducts.first(where: { $0.id == option.storeProductId }), !product.isSubscription { + let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(option.months) / Float(shortestOptionPrice.0) + let discountValue = Int(round((1.0 - fraction) * 20.0) * 5.0) + premiumProducts.append(PremiumGiftProduct(giftOption: option, storeProduct: product, discount: discountValue > 0 ? discountValue : nil)) + } + } + self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months }) } - self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months }) } self.starGifts = starGifts?.compactMap { gift in diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index a5e6cf3e74..58b2e7ec9a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -616,10 +616,12 @@ final class GiftSetupScreenComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let isSelfGift = component.peerId == component.context.account.peerId + let navigationTitleSize = self.navigationTitle.update( transition: transition, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.Gift_Send_TitleTo(peerName).string, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), + text: .plain(NSAttributedString(string: isSelfGift ? environment.strings.Gift_SendSelf_Title : environment.strings.Gift_Send_TitleTo(peerName).string, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), horizontalAlignment: .center )), environment: {}, @@ -946,7 +948,7 @@ final class GiftSetupScreenComponent: Component { header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environment.strings.Gift_Send_HideMyName_Info(peerName, peerName).string, + string: isSelfGift ? environment.strings.Gift_SendSelf_HideMyName_Info : environment.strings.Gift_Send_HideMyName_Info(peerName, peerName).string, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -958,7 +960,7 @@ final class GiftSetupScreenComponent: Component { title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environment.strings.Gift_Send_HideMyName, + string: isSelfGift ? environment.strings.Gift_SendSelf_HideMyName : environment.strings.Gift_Send_HideMyName, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 9c5f2e592d..2ed150b46c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1432,7 +1432,8 @@ private final class GiftViewSheetContent: CombinedComponent { color: theme.list.itemCheckColors.fillColor, foreground: theme.list.itemCheckColors.foregroundColor, pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 + cornerRadius: 10.0, + isShimmering: true ), content: AnyComponentWithIdentity( id: AnyHashable("freeUpgrade"), diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index 6e5d83c23e..4b71802776 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -130,7 +130,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let optionSpacing: CGFloat = 10.0 let sideInset = params.sideInset + 16.0 - let itemsInRow = min(starsProducts.count, 3) + let itemsInRow = max(1, min(starsProducts.count, 3)) let optionWidth = (params.size.width - sideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow) let starsOptionSize = CGSize(width: optionWidth, height: optionWidth)