diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index d5d9c5fbd6..21d6f91c16 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1531,8 +1531,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { ApplicationSpecificNotice.dismissedPremiumColorsBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedMessageTagsBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedLastSeenBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedMessagePrivacyBadge(accountManager: context.sharedContext.accountManager) - ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge, dismissedLastSeenBadge, dismissedMessagePrivacyBadge in + ApplicationSpecificNotice.dismissedMessagePrivacyBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager) + ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge, dismissedLastSeenBadge, dismissedMessagePrivacyBadge, dismissedBusinessBadge in guard let self else { return } @@ -1552,8 +1553,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { if !dismissedMessagePrivacyBadge { newPerks.append(PremiumPerk.messagePrivacy.identifier) } - //TODO: - newPerks.append(PremiumPerk.business.identifier) + if !dismissedBusinessBadge { + newPerks.append(PremiumPerk.business.identifier) + } self.newPerks = newPerks self.updated() }) @@ -1937,17 +1939,31 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { if case .business = context.component.mode, case .business = perk { continue } + + let isNew = state.newPerks.contains(perk.identifier) + let titleComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: perk.title(strings: strings), + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + )) + + let titleCombinedComponent: AnyComponent + if isNew { + titleCombinedComponent = AnyComponent(HStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: titleComponent), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(BadgeComponent(color: gradientColors[i], text: strings.Premium_New))) + ], spacing: 5.0)) + } else { + titleCombinedComponent = AnyComponent(HStack([AnyComponentWithIdentity(id: AnyHashable(0), component: titleComponent)], spacing: 0.0)) + } + perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, title: AnyComponent(VStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: perk.title(strings: strings), - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 0 - ))), + AnyComponentWithIdentity(id: AnyHashable(0), component: titleCombinedComponent), AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: perk.subtitle(strings: strings), @@ -2013,6 +2029,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() case .business: demoSubject = .business + let _ = ApplicationSpecificNotice.setDismissedBusinessBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() default: demoSubject = .doubleLimits } @@ -3721,3 +3738,61 @@ private final class EmojiActionIconComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + +private final class BadgeComponent: CombinedComponent { + let color: UIColor + let text: String + + init( + color: UIColor, + text: String + ) { + self.color = color + self.text = text + } + + static func ==(lhs: BadgeComponent, rhs: BadgeComponent) -> Bool { + if lhs.color != rhs.color { + return false + } + if lhs.text != rhs.text { + return false + } + return true + } + + static var body: Body { + let badgeBackground = Child(RoundedRectangle.self) + let badgeText = Child(MultilineTextComponent.self) + + return { context in + let component = context.component + + let badgeText = badgeText.update( + component: MultilineTextComponent(text: .plain(NSAttributedString(string: component.text, font: Font.semibold(11.0), textColor: .white))), + availableSize: context.availableSize, + transition: context.transition + ) + + let badgeSize = CGSize(width: badgeText.size.width + 7.0, height: 16.0) + let badgeBackground = badgeBackground.update( + component: RoundedRectangle( + color: component.color, + cornerRadius: 5.0 + ), + availableSize: badgeSize, + transition: context.transition + ) + + context.add(badgeBackground + .position(CGPoint(x: badgeSize.width / 2.0, y: badgeSize.height / 2.0)) + ) + + context.add(badgeText + .position(CGPoint(x: badgeSize.width / 2.0, y: badgeSize.height / 2.0)) + ) + + return badgeSize + } + } +} diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 3ae7dc6294..4fd0a10755 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -384,7 +384,27 @@ public class PremiumLimitsListScreen: ViewController { let theme = self.presentationData.theme let strings = self.presentationData.strings - if let stickers = self.stickers, let appIcons = self.appIcons, let configuration = self.promoConfiguration { + let videos: [String: TelegramMediaFile] = self.promoConfiguration?.videos ?? [:] + let stickers = self.stickers ?? [] + let appIcons = self.appIcons ?? [] + + let isReady: Bool + switch controller.subject { + case .premiumStickers: + isReady = !stickers.isEmpty + case .appIcons: + isReady = !appIcons.isEmpty + case .stories: + isReady = true + case .doubleLimits: + isReady = true + case .business: + isReady = true + default: + isReady = !videos.isEmpty + } + + if isReady { let context = controller.context let textColor = theme.actionSheet.primaryTextColor @@ -482,7 +502,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .bottom, - videoFile: configuration.videos["more_upload"], + videoFile: videos["more_upload"], decoration: .dataRain )), title: strings.Premium_UploadSize, @@ -500,7 +520,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["faster_download"], + videoFile: videos["faster_download"], decoration: .fasterStars )), title: strings.Premium_FasterSpeed, @@ -518,7 +538,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["voice_to_text"], + videoFile: videos["voice_to_text"], decoration: .badgeStars )), title: strings.Premium_VoiceToText, @@ -536,7 +556,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .bottom, - videoFile: configuration.videos["no_ads"], + videoFile: videos["no_ads"], decoration: .swirlStars )), title: strings.Premium_NoAds, @@ -554,7 +574,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["infinite_reactions"], + videoFile: videos["infinite_reactions"], decoration: .swirlStars )), title: strings.Premium_InfiniteReactions, @@ -593,7 +613,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["emoji_status"], + videoFile: videos["emoji_status"], decoration: .badgeStars )), title: strings.Premium_EmojiStatus, @@ -611,7 +631,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["advanced_chat_management"], + videoFile: videos["advanced_chat_management"], decoration: .swirlStars )), title: strings.Premium_ChatManagement, @@ -629,7 +649,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["profile_badge"], + videoFile: videos["profile_badge"], decoration: .badgeStars )), title: strings.Premium_Badge, @@ -647,7 +667,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["animated_userpics"], + videoFile: videos["animated_userpics"], decoration: .swirlStars )), title: strings.Premium_Avatar, @@ -681,7 +701,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .bottom, - videoFile: configuration.videos["animated_emoji"], + videoFile: videos["animated_emoji"], decoration: .emoji )), title: strings.Premium_AnimatedEmoji, @@ -700,7 +720,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["translations"], + videoFile: videos["translations"], decoration: .hello )), title: strings.Premium_Translation, @@ -718,7 +738,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["peer_colors"], + videoFile: videos["peer_colors"], decoration: .badgeStars )), title: strings.Premium_Colors, @@ -737,7 +757,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["wallpapers"], + videoFile: videos["wallpapers"], decoration: .swirlStars )), title: strings.Premium_Wallpapers, @@ -756,7 +776,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["saved_tags"], + videoFile: videos["saved_tags"], decoration: .tag )), title: strings.Premium_MessageTags, @@ -775,7 +795,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["last_seen"], + videoFile: videos["last_seen"], decoration: .badgeStars )), title: strings.Premium_LastSeen, @@ -794,7 +814,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["message_privacy"], + videoFile: videos["message_privacy"], decoration: .swirlStars )), title: strings.Premium_MessagePrivacy, @@ -846,7 +866,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["business_location"], + videoFile: videos["business_location"], decoration: .business )), title: strings.Business_Location, @@ -866,7 +886,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["business_hours"], + videoFile: videos["business_hours"], decoration: .business )), title: strings.Business_OpeningHours, @@ -886,7 +906,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["quick_replies"], + videoFile: videos["quick_replies"], decoration: .business )), title: strings.Business_QuickReplies, @@ -906,7 +926,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["greeting_message"], + videoFile: videos["greeting_message"], decoration: .business )), title: strings.Business_GreetingMessages, @@ -926,7 +946,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["away_message"], + videoFile: videos["away_message"], decoration: .business )), title: strings.Business_AwayMessages, @@ -946,7 +966,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["business_bots"], + videoFile: videos["business_bots"], decoration: .business )), title: strings.Business_Chatbots, diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 627928f5ac..7eee7c482b 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -199,6 +199,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case savedMessageTagLabelSuggestion = 65 case dismissedLastSeenBadge = 66 case dismissedMessagePrivacyBadge = 67 + case dismissedBusinessBadge = 68 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -529,6 +530,10 @@ private struct ApplicationSpecificNoticeKeys { static func dismissedMessagePrivacyBadge() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedMessagePrivacyBadge.key) } + + static func dismissedBusinessBadge() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessBadge.key) + } } public struct ApplicationSpecificNotice { @@ -2223,4 +2228,25 @@ public struct ApplicationSpecificNotice { } |> take(1) } + + public static func setDismissedBusinessBadge(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessBadge(), entry) + } + } + |> ignoreValues + } + + public static func dismissedBusinessBadge(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessBadge()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + |> take(1) + } } diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift index ebe68f2d33..5cd33daa87 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift @@ -345,16 +345,17 @@ public final class AvatarStoryIndicatorComponent: Component { } let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! - - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + if let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations) { + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + } } } } else { let lineWidth: CGFloat = component.hasUnseen ? component.activeLineWidth : component.inactiveLineWidth context.setLineWidth(lineWidth) if component.isRoundedRect { - context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5), cornerRadius: floor(diameter * 0.25)).cgPath) + let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5), cornerRadius: floor(diameter * 0.27)) + context.addPath(path.cgPath) } else { context.addEllipse(in: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)) }