From 78a09a38b3f27e06119d58399cb81dfddd525c24 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 16 Nov 2019 08:31:47 +0400 Subject: [PATCH] Add support for gradient wallpapers Implement new colors selection UI --- .../Sources/SegmentedControlNode.swift | 18 +- .../Themes/CustomWallpaperPicker.swift | 9 +- .../Sources/Themes/EditThemeController.swift | 20 +- .../Themes/SettingsThemeWallpaperNode.swift | 9 +- .../Themes/ThemeAccentColorController.swift | 123 +++-- .../ThemeAccentColorControllerNode.swift | 232 ++++++--- .../Themes/ThemeColorSegmentedTitleView.swift | 55 ++ .../Sources/Themes/ThemeGridController.swift | 12 +- .../Themes/ThemeGridControllerNode.swift | 14 +- .../Themes/ThemePreviewController.swift | 19 +- .../Themes/ThemeSettingsController.swift | 31 +- .../Themes/WallpaperColorPanelNode.swift | 468 +++++++++++++++--- .../Themes/WallpaperColorPickerNode.swift | 19 +- .../Themes/WallpaperGalleryController.swift | 9 +- .../Sources/Themes/WallpaperGalleryItem.swift | 17 +- .../SyncCore/Sources/TelegramWallpaper.swift | 16 +- .../ChatControllerBackgroundNode.swift | 10 + .../Sources/PresentationData.swift | 44 +- .../ThemeColorAddIcon.imageset/Contents.json | 12 + .../ic_input_add.pdf | Bin 0 -> 3979 bytes .../Contents.json | 12 + .../ic_input_close.pdf | Bin 0 -> 4022 bytes .../ThemeColorSwapIcon.imageset/Contents.json | 12 + .../ic_input_change.pdf | Bin 0 -> 4296 bytes .../WallpaperColorIcon.imageset/Contents.json | 22 - .../WallpaperColorIcon.imageset/color@2x.png | Bin 2486 -> 0 bytes .../WallpaperColorIcon.imageset/color@3x.png | Bin 3905 -> 0 bytes .../ChatMessageInteractiveMediaNode.swift | 2 +- .../TelegramUI/ThemeUpdateManager.swift | 16 +- .../TelegramUI/UpgradedAccounts.swift | 2 +- .../TelegramUI/WallpaperUploadManager.swift | 4 +- .../Sources/PresentationThemeSettings.swift | 56 ++- .../Sources/WallpaperResources.swift | 38 +- 33 files changed, 990 insertions(+), 311 deletions(-) create mode 100644 submodules/SettingsUI/Sources/Themes/ThemeColorSegmentedTitleView.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/ThemeColorAddIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/ThemeColorAddIcon.imageset/ic_input_add.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/ThemeColorRemoveIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/ThemeColorRemoveIcon.imageset/ic_input_close.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/ic_input_change.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/color@2x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/color@3x.png diff --git a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift index ba95b378b7..b7cda8830b 100644 --- a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift +++ b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift @@ -144,6 +144,7 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg } public var selectedIndexChanged: (Int) -> Void = { _ in } + public var selectedIndexShouldChange: (Int) -> Bool = { _ in return true } public init(theme: SegmentedControlTheme, items: [SegmentedControlItem], selectedIndex: Int) { self.theme = theme @@ -347,6 +348,10 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg return } + guard self.selectedIndexShouldChange(index) else { + return + } + self._selectedIndex = index self.selectedIndexChanged(index) if let layout = self.validLayout { @@ -355,8 +360,7 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg } public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - let location = gestureRecognizer.location(in: self.view) - return self.selectionNode.frame.contains(location) + return self.selectionNode.frame.contains(gestureRecognizer.location(in: self.view)) } @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { @@ -382,8 +386,14 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg case .ended: if let gestureSelectedIndex = self.gestureSelectedIndex { if gestureSelectedIndex != self.selectedIndex { - self._selectedIndex = gestureSelectedIndex - self.selectedIndexChanged(self._selectedIndex) + if self.selectedIndexShouldChange(gestureSelectedIndex) { + self._selectedIndex = gestureSelectedIndex + self.selectedIndexChanged(self._selectedIndex) + } else { + if let layout = self.validLayout { + let _ = self.updateLayout(layout, transition: .animated(duration: 0.35, curve: .slide)) + } + } } self.gestureSelectedIndex = nil } diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index 4ad1e1a7d6..99e4b70c23 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -138,6 +138,7 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + let autoNightModeTriggered = context.sharedContext.currentPresentationData.with {$0 }.autoNightModeTriggered let accountManager = context.sharedContext.accountManager let account = context.account let updateWallpaper: (TelegramWallpaper) -> Void = { wallpaper in @@ -155,8 +156,12 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE let _ = (updatePresentationThemeSettingsInteractively(accountManager: accountManager, { current in var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers - themeSpecificChatWallpapers[current.theme.index] = wallpaper - return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + if autoNightModeTriggered { + themeSpecificChatWallpapers[current.automaticThemeSwitchSetting.theme.index] = wallpaper + } else { + themeSpecificChatWallpapers[current.theme.index] = wallpaper + } + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) })).start() } diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index 180b9f52c6..45bdcd0ae4 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -464,11 +464,11 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) } let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper)) - var chatWallpaper = current.chatWallpaper - if let theme = theme { - chatWallpaper = resolvedWallpaper ?? theme.chat.defaultWallpaper - } - return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + themeSpecificChatWallpapers[themeReference.index] = nil + + return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) } |> deliverOnMainQueue).start(completed: { if !hasCustomFile { @@ -505,11 +505,11 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) } let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper)) - var chatWallpaper = current.chatWallpaper - if let theme = theme { - chatWallpaper = resolvedWallpaper ?? theme.chat.defaultWallpaper - } - return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + themeSpecificChatWallpapers[themeReference.index] = nil + + return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) } |> deliverOnMainQueue).start(completed: { if let themeResource = themeResource, !hasCustomFile { diff --git a/submodules/SettingsUI/Sources/Themes/SettingsThemeWallpaperNode.swift b/submodules/SettingsUI/Sources/Themes/SettingsThemeWallpaperNode.swift index c7badf0e10..95185f3e78 100644 --- a/submodules/SettingsUI/Sources/Themes/SettingsThemeWallpaperNode.swift +++ b/submodules/SettingsUI/Sources/Themes/SettingsThemeWallpaperNode.swift @@ -99,7 +99,12 @@ final class SettingsThemeWallpaperNode: ASDisplayNode { self.backgroundNode.isHidden = false self.backgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: color)) } - + case let .gradient(topColor, bottomColor): + self.imageNode.isHidden = false + self.backgroundNode.isHidden = true + self.imageNode.setSignal(gradientImage([UIColor(rgb: UInt32(bitPattern: topColor)), UIColor(rgb: UInt32(bitPattern: bottomColor))])) + let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: CGSize(), boundingSize: size, intrinsicInsets: UIEdgeInsets())) + apply() case let .image(representations, _): self.imageNode.isHidden = false self.backgroundNode.isHidden = true @@ -144,7 +149,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode { } } else if let wallpaper = self.wallpaper { switch wallpaper { - case .builtin, .color: + case .builtin, .color, .gradient: let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: CGSize(), boundingSize: size, intrinsicInsets: UIEdgeInsets())) apply() case let .image(representations, _): diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift index ec8f79206c..93d383e702 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift @@ -15,8 +15,9 @@ private let colors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, final class ThemeAccentColorController: ViewController { private let context: AccountContext private let currentTheme: PresentationThemeReference - private let initialColor: UIColor - private let initialTheme: PresentationTheme + private let section: ThemeColorSection + + let segmentedTitleView: ThemeColorSegmentedTitleView private var controllerNode: ThemeAccentColorControllerNode { return self.displayNode as! ThemeAccentColorControllerNode @@ -24,30 +25,41 @@ final class ThemeAccentColorController: ViewController { private var presentationData: PresentationData - init(context: AccountContext, currentTheme: PresentationThemeReference, currentColor: UIColor?) { + private let _ready = Promise() + override public var ready: Promise { + return self._ready + } + + init(context: AccountContext, currentTheme: PresentationThemeReference, section: ThemeColorSection) { self.context = context self.currentTheme = currentTheme - - var color: UIColor - if let currentColor = currentColor { - color = currentColor - } - else if let randomColor = colors.randomElement() { - color = UIColor(rgb: UInt32(bitPattern: randomColor)) - } else { - color = defaultDayAccentColor - } - self.initialColor = color - self.initialTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: currentTheme, accentColor: color, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: nil, preview: true) ?? defaultPresentationTheme - + self.section = section self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.initialTheme, presentationStrings: self.presentationData.strings)) +// var color: UIColor +// if let currentColor = currentColor { +// color = currentColor +// } else if let randomColor = colors.randomElement() { +// color = UIColor(rgb: UInt32(bitPattern: randomColor)) +// } else { +// color = defaultDayAccentColor +// } - self.title = self.presentationData.strings.AccentColor_Title + self.segmentedTitleView = ThemeColorSegmentedTitleView(theme: self.presentationData.theme, strings: self.presentationData.strings, selectedSection: .accent) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings)) + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + + self.segmentedTitleView.sectionUpdated = { [weak self] section in + if let strongSelf = self { + strongSelf.controllerNode.updateSection(section) + } + } + + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) + self.navigationItem.titleView = self.segmentedTitleView } required init(coder aDecoder: NSCoder) { @@ -57,11 +69,11 @@ final class ThemeAccentColorController: ViewController { override func loadDisplayNode() { super.loadDisplayNode() - self.displayNode = ThemeAccentColorControllerNode(context: self.context, currentTheme: self.currentTheme, color: self.initialColor, theme: self.initialTheme, dismiss: { [weak self] in + self.displayNode = ThemeAccentColorControllerNode(context: self.context, currentTheme: self.currentTheme, theme: self.presentationData.theme, dismiss: { [weak self] in if let strongSelf = self { strongSelf.dismiss() } - }, apply: { [weak self] in + }, apply: { [weak self] state in if let strongSelf = self { let context = strongSelf.context let _ = (updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in @@ -70,22 +82,19 @@ final class ThemeAccentColorController: ViewController { if autoNightModeTriggered { currentTheme = current.automaticThemeSwitchSetting.theme } - - var themeSpecificAccentColors = current.themeSpecificAccentColors - let color = PresentationThemeAccentColor(baseColor: .custom, value: Int32(bitPattern: strongSelf.controllerNode.color)) - themeSpecificAccentColors[currentTheme.index] = color var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + var themeSpecificAccentColors = current.themeSpecificAccentColors + //let color = PresentationThemeAccentColor(baseColor: .custom, value: Int32(bitPattern: strongSelf.controllerNode.color)) + //themeSpecificAccentColors[currentTheme.index] = color - let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: currentTheme, accentColor: UIColor(rgb: strongSelf.controllerNode.color), serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: color.baseColor) ?? defaultPresentationTheme - var chatWallpaper = current.chatWallpaper + let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: currentTheme, accentColor: nil, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: nil) ?? defaultPresentationTheme if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.hasWallpaper { } else { - chatWallpaper = theme.chat.defaultWallpaper - themeSpecificChatWallpapers[currentTheme.index] = chatWallpaper + themeSpecificChatWallpapers[currentTheme.index] = theme.chat.defaultWallpaper } - return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: strongSelf.currentTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) |> deliverOnMainQueue).start(completed: { [weak self] in if let strongSelf = self { strongSelf.dismiss() @@ -98,11 +107,59 @@ final class ThemeAccentColorController: ViewController { strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: theme, presentationStrings: strongSelf.presentationData.strings)) } } - self.displayNodeDidLoad() - } - - private func updateStrings() { + let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) + |> deliverOnMainQueue).start(next: { [weak self] sharedData in + guard let strongSelf = self else { + return + } + let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings + + let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered + let themeReference: PresentationThemeReference + if autoNightModeTriggered { + themeReference = settings.automaticThemeSwitchSetting.theme + } else { + themeReference = settings.theme + } + + let accentColor = settings.themeSpecificAccentColors[themeReference.index]?.color ?? defaultDayAccentColor + let wallpaper: TelegramWallpaper + if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] { + wallpaper = customWallpaper + } else { + let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: nil, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: nil) ?? defaultPresentationTheme + wallpaper = theme.chat.defaultWallpaper + } + + let backgroundColors: (UIColor, UIColor?)? + if case let .color(color) = wallpaper { + backgroundColors = (UIColor(rgb: UInt32(bitPattern: color)), nil) + } else if case let .gradient(topColor, bottomColor) = wallpaper { + backgroundColors = (UIColor(rgb: UInt32(bitPattern: topColor)), UIColor(rgb: UInt32(bitPattern: bottomColor))) + } else { + backgroundColors = nil + } + + let messageColors: (UIColor, UIColor?)? + if let bubbleColors = settings.themeSpecificBubbleColors[themeReference.index] { + if let bottomColor = bubbleColors.optionalColor { + messageColors = (UIColor(rgb: UInt32(bitPattern: bubbleColors.color)), UIColor(rgb: UInt32(bitPattern: bottomColor))) + } else { + messageColors = (UIColor(rgb: UInt32(bitPattern: bubbleColors.color)), nil) + } + } else { + messageColors = nil + } + + let initialState = ThemeColorState(section: strongSelf.section, accentColor: accentColor, backgroundColors: backgroundColors, messagesColors: messageColors) + + strongSelf.controllerNode.updateState({ _ in + return initialState + }, animated: false) + strongSelf._ready.set(.single(true)) + }) + self.displayNodeDidLoad() } override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index c8f4087e97..c5c3da0bac 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -25,6 +25,33 @@ private func generateMaskImage(color: UIColor) -> UIImage? { context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 80.0), options: CGGradientDrawingOptions()) }) } + +enum ThemeColorSection: Int { + case accent + case background + case messages +} + +struct ThemeColorState { + fileprivate var section: ThemeColorSection? + var accentColor: UIColor + var backgroundColors: (UIColor, UIColor?)? + var messagesColors: (UIColor, UIColor?)? + + init() { + self.section = nil + self.accentColor = .clear + self.backgroundColors = nil + self.messagesColors = nil + } + + init(section: ThemeColorSection, accentColor: UIColor, backgroundColors: (UIColor, UIColor?)?, messagesColors: (UIColor, UIColor?)?) { + self.section = section + self.accentColor = accentColor + self.backgroundColors = backgroundColors + self.messagesColors = messagesColors + } +} final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate { private let context: AccountContext @@ -32,6 +59,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate private let currentTheme: PresentationThemeReference private var presentationData: PresentationData + private var state: ThemeColorState + private let referenceTimestamp: Int32 private let scrollNode: ASScrollNode @@ -51,20 +80,20 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate private var validLayout: (ContainerViewLayout, CGFloat, CGFloat)? private var serviceColorDisposable: Disposable? - private var colorDisposable: Disposable? - private let colorValue = ValuePromise(ignoreRepeated: true) + private var colorsDisposable: Disposable? + private let colors = Promise<(UIColor, (UIColor, UIColor?)?, (UIColor, UIColor?)?)>() + + private let themePromise = Promise() var themeUpdated: ((PresentationTheme) -> Void)? - var color: UInt32 { - return self.colorPanelNode.color.rgb - } - init(context: AccountContext, currentTheme: PresentationThemeReference, color: UIColor, theme: PresentationTheme, dismiss: @escaping () -> Void, apply: @escaping () -> Void) { + init(context: AccountContext, currentTheme: PresentationThemeReference, theme: PresentationTheme, dismiss: @escaping () -> Void, apply: @escaping (ThemeColorState) -> Void) { self.context = context self.currentTheme = currentTheme + self.state = ThemeColorState() + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.theme = theme - self.colorValue.set(color) let calendar = Calendar(identifier: .gregorian) var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: Date()) @@ -76,21 +105,20 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate self.scrollNode = ASScrollNode() self.pageControlBackgroundNode = ASDisplayNode() self.pageControlBackgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3) - self.pageControlBackgroundNode.cornerRadius = 8.0 + self.pageControlBackgroundNode.cornerRadius = 10.5 - self.pageControlNode = PageControlNode(dotColor: self.theme.chatList.unreadBadgeActiveBackgroundColor, inactiveDotColor: self.presentationData.theme.list.pageIndicatorInactiveColor) + self.pageControlNode = PageControlNode(dotSpacing: 7.0, dotColor: .white, inactiveDotColor: UIColor.white.withAlphaComponent(0.4)) self.chatListBackgroundNode = ASDisplayNode() self.chatBackgroundNode = WallpaperBackgroundNode() self.chatBackgroundNode.displaysAsynchronously = false if case .color = self.presentationData.chatWallpaper { } else { - self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: theme, wallpaper: self.presentationData.chatWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) + self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: theme, wallpaper: self.presentationData.chatWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: false) self.chatBackgroundNode.motionEnabled = self.presentationData.chatWallpaper.settings?.motion ?? false } self.colorPanelNode = WallpaperColorPanelNode(theme: self.theme, strings: self.presentationData.strings) - self.colorPanelNode.color = color self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.theme, strings: self.presentationData.strings) self.maskNode = ASImageNode() @@ -105,13 +133,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate }) self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - self.chatListBackgroundNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor - if case let .color(value) = self.presentationData.theme.chat.defaultWallpaper { - self.chatBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) - } - self.pageControlNode.isUserInteractionEnabled = false self.pageControlNode.pagesCount = 2 @@ -125,67 +148,100 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate self.scrollNode.addSubnode(self.chatListBackgroundNode) self.scrollNode.addSubnode(self.chatBackgroundNode) - self.colorPanelNode.colorChanged = { [weak self] color, ended in - if let strongSelf = self { - strongSelf.colorValue.set(color) + self.colorPanelNode.colorsChanged = { [weak self] firstColor, secondColor, _ in + if let strongSelf = self, let section = strongSelf.state.section { + switch section { + case .accent: + strongSelf.updateState({ current in + var updated = current + updated.accentColor = firstColor + return updated + }) + case .background: + strongSelf.updateState({ current in + var updated = current + updated.backgroundColors = (firstColor, secondColor) + return updated + }) + case .messages: + strongSelf.updateState({ current in + var updated = current + updated.messagesColors = (firstColor, secondColor) + return updated + }) + } } } self.toolbarNode.cancel = { dismiss() } - self.toolbarNode.done = { - apply() + self.toolbarNode.done = { [weak self] in + if let strongSelf = self { + apply(strongSelf.state) + } } - self.colorDisposable = (self.colorValue.get() + self.colorsDisposable = (self.colors.get() |> deliverOn(Queue.concurrentDefaultQueue()) - |> map { color -> PresentationTheme in - let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: currentTheme, accentColor: color, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: nil, preview: true) ?? defaultPresentationTheme + |> map { accentColor, backgroundColors, messagesColors -> (PresentationTheme, TelegramWallpaper?) in + let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: currentTheme, accentColor: accentColor, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: nil, preview: true) ?? defaultPresentationTheme - let wallpaper = context.sharedContext.currentPresentationData.with { $0 }.chatWallpaper + var wallpaper = context.sharedContext.currentPresentationData.with { $0 }.chatWallpaper + if let backgroundColors = backgroundColors { + if let bottomColor = backgroundColors.1 { + wallpaper = .gradient(Int32(bitPattern: backgroundColors.0.rgb), Int32(bitPattern: bottomColor.rgb)) + } else { + wallpaper = .color(Int32(bitPattern: backgroundColors.0.rgb)) + } + } let _ = PresentationResourcesChat.principalGraphics(mediaBox: context.account.postbox.mediaBox, knockoutWallpaper: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper, theme: theme, wallpaper: wallpaper, gradientBubbles: context.sharedContext.immediateExperimentalUISettings.gradientBubbles) - return theme + return (theme, wallpaper) } - |> deliverOnMainQueue).start(next: { [weak self] theme in - if let strongSelf = self { - strongSelf.theme = theme - strongSelf.themeUpdated?(theme) - - strongSelf.colorPanelNode.updateTheme(theme) - strongSelf.toolbarNode.updateThemeAndStrings(theme: theme, strings: strongSelf.presentationData.strings) - strongSelf.maskNode.image = generateMaskImage(color: theme.chatList.backgroundColor) - - if case let .color(value) = theme.chat.defaultWallpaper { - strongSelf.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) - strongSelf.chatListBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) - strongSelf.chatBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) - } - - if let (layout, navigationBarHeight, messagesBottomInset) = strongSelf.validLayout { - strongSelf.pageControlNode.dotColor = theme.chatList.unreadBadgeActiveBackgroundColor - strongSelf.pageControlNode.inactiveDotColor = theme.list.pageIndicatorInactiveColor - strongSelf.updateChatsLayout(layout: layout, topInset: navigationBarHeight, transition: .immediate) - strongSelf.updateMessagesLayout(layout: layout, bottomInset: messagesBottomInset, transition: .immediate) - } + |> deliverOnMainQueue).start(next: { [weak self] theme, wallpaper in + guard let strongSelf = self else { + return + } + strongSelf.theme = theme + strongSelf.themeUpdated?(theme) + strongSelf.themePromise.set(.single(theme)) + + strongSelf.colorPanelNode.updateTheme(theme) + strongSelf.toolbarNode.updateThemeAndStrings(theme: theme, strings: strongSelf.presentationData.strings) + strongSelf.maskNode.image = generateMaskImage(color: theme.chatList.backgroundColor) + + if case let .color(value) = theme.chat.defaultWallpaper { + strongSelf.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) + strongSelf.chatListBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) + strongSelf.chatBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) + } + + if let (layout, navigationBarHeight, messagesBottomInset) = strongSelf.validLayout { + strongSelf.pageControlNode.dotColor = UIColor.white + strongSelf.pageControlNode.inactiveDotColor = UIColor.white.withAlphaComponent(0.4) + strongSelf.updateChatsLayout(layout: layout, topInset: navigationBarHeight, transition: .immediate) + strongSelf.updateMessagesLayout(layout: layout, bottomInset: messagesBottomInset, transition: .immediate) } }) - self.serviceColorDisposable = (chatServiceBackgroundColor(wallpaper: self.presentationData.chatWallpaper, mediaBox: context.account.postbox.mediaBox) + self.serviceColorDisposable = (self.themePromise.get() + |> mapToSignal { theme -> Signal in + return chatServiceBackgroundColor(wallpaper: self.presentationData.chatWallpaper, mediaBox: context.account.postbox.mediaBox) + } |> deliverOnMainQueue).start(next: { [weak self] color in if let strongSelf = self { - if strongSelf.presentationData.chatWallpaper.hasWallpaper { + //if strongSelf.presentationData.chatWallpaper.hasWallpaper { strongSelf.pageControlBackgroundNode.backgroundColor = color - } else { - strongSelf.pageControlBackgroundNode.backgroundColor = .clear - } + //} else { + // strongSelf.pageControlBackgroundNode.backgroundColor = .clear + //} } }) } deinit { - self.colorDisposable?.dispose() + self.colorsDisposable?.dispose() self.serviceColorDisposable?.dispose() } @@ -207,6 +263,48 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } } + func updateState(_ f: (ThemeColorState) -> ThemeColorState, animated: Bool = false) { + let previousState = self.state + self.state = f(self.state) + + let colorsChanged = previousState.accentColor != self.state.accentColor + if colorsChanged { + self.colors.set(.single((self.state.accentColor, self.state.backgroundColors, self.state.messagesColors))) + } + + let sectionChanged = previousState.section != self.state.section + if sectionChanged, let section = self.state.section { + let firstColor: UIColor + let secondColor: UIColor? + switch section { + case .accent: + firstColor = self.state.accentColor ?? .white + secondColor = nil + case .background: + firstColor = self.state.backgroundColors?.0 ?? .white + secondColor = self.state.backgroundColors?.1 ?? .white + case .messages: + firstColor = self.state.messagesColors?.0 ?? .blue + secondColor = self.state.messagesColors?.1 ?? .blue + } + let colorPanelState = WallpaperColorPanelNodeState(selection: .first, firstColor: firstColor, firstColorRemovable: self.state.section == .messages, secondColor: secondColor, secondColorAvailable: self.state.section != .accent) + self.colorPanelNode.updateState({ _ in + return colorPanelState + }, animated: animated) + if let (layout, navigationBarHeight, _) = self.validLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate) + } + } + } + + func updateSection(_ section: ThemeColorSection) { + self.updateState({ current in + var updated = current + updated.section = section + return updated + }, animated: true) + } + private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) { var items: [ChatListItem] = [] @@ -361,6 +459,21 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate self.scrollNode.view.contentSize = CGSize(width: bounds.width * 2.0, height: bounds.height) + var pageControlAlpha: CGFloat = 1.0 + if self.state.section != .accent { + pageControlAlpha = 0.0 + } + self.scrollNode.view.isScrollEnabled = pageControlAlpha > 0.0 + + var messagesTransition = transition + if !self.scrollNode.view.isScrollEnabled && self.scrollNode.view.contentOffset.x > 0.0 { + var bounds = self.scrollNode.bounds + bounds.origin.x = 0.0 + transition.updateBounds(node: scrollNode, bounds: bounds) + messagesTransition = .immediate + self.pageControlNode.setPage(0.0) + } + transition.updateFrame(node: self.toolbarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: 49.0 + layout.intrinsicInsets.bottom))) self.toolbarNode.updateLayout(size: CGSize(width: layout.size.width, height: 49.0), layout: layout, transition: transition) @@ -373,17 +486,22 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate transition.updateFrame(node: self.colorPanelNode, frame: colorPanelFrame) self.colorPanelNode.updateLayout(size: colorPanelFrame.size, keyboardHeight: layout.inputHeight ?? 0.0, transition: transition) - let messagesBottomInset = bottomInset + 36.0 + var messagesBottomInset = bottomInset + if pageControlAlpha > 0.0 { + messagesBottomInset += 37.0 + } self.updateChatsLayout(layout: layout, topInset: navigationBarHeight, transition: transition) - self.updateMessagesLayout(layout: layout, bottomInset: messagesBottomInset, transition: transition) + self.updateMessagesLayout(layout: layout, bottomInset: messagesBottomInset, transition: messagesTransition) self.validLayout = (layout, navigationBarHeight, messagesBottomInset) let pageControlSize = self.pageControlNode.measure(CGSize(width: bounds.width, height: 100.0)) - let pageControlFrame = CGRect(origin: CGPoint(x: floor((bounds.width - pageControlSize.width) / 2.0), y: layout.size.height - bottomInset - 27.0), size: pageControlSize) + let pageControlFrame = CGRect(origin: CGPoint(x: floor((bounds.width - pageControlSize.width) / 2.0), y: layout.size.height - bottomInset - 28.0), size: pageControlSize) self.pageControlNode.frame = pageControlFrame - self.pageControlBackgroundNode.frame = CGRect(x: pageControlFrame.minX - 11.0, y: pageControlFrame.minY - 12.0, width: pageControlFrame.width + 22.0, height: 30.0) + self.pageControlBackgroundNode.frame = CGRect(x: pageControlFrame.minX - 7.0, y: pageControlFrame.minY - 7.0, width: pageControlFrame.width + 14.0, height: 21.0) + transition.updateAlpha(node: self.pageControlNode, alpha: pageControlAlpha) + transition.updateAlpha(node: self.pageControlBackgroundNode, alpha: pageControlAlpha) transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - bottomInset - 80.0, width: bounds.width, height: 80.0)) } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorSegmentedTitleView.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorSegmentedTitleView.swift new file mode 100644 index 0000000000..816849e271 --- /dev/null +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorSegmentedTitleView.swift @@ -0,0 +1,55 @@ +import Foundation +import UIKit +import SegmentedControlNode +import TelegramPresentationData + +final class ThemeColorSegmentedTitleView: UIView { + private let segmentedControlNode: SegmentedControlNode + + var theme: PresentationTheme { + didSet { + self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) + } + } + + var index: Int { + get { + return self.segmentedControlNode.selectedIndex + } + set { + self.segmentedControlNode.selectedIndex = newValue + } + } + + var sectionUpdated: ((ThemeColorSection) -> Void)? + var shouldUpdateSection: ((ThemeColorSection) -> Void)? + + init(theme: PresentationTheme, strings: PresentationStrings, selectedSection: ThemeColorSection) { + self.theme = theme + + let sections = [strings.Theme_Colors_Accent, strings.Theme_Colors_Background, strings.Theme_Colors_Messages] + self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: sections.map { SegmentedControlItem(title: $0) }, selectedIndex: selectedSection.rawValue) + + super.init(frame: CGRect()) + + self.segmentedControlNode.selectedIndexChanged = { [weak self] index in + if let section = ThemeColorSection(rawValue: index) { + self?.sectionUpdated?(section) + } + } + + self.addSubnode(self.segmentedControlNode) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + let size = self.bounds.size + let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: size.width + 20.0), transition: .immediate) + self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floor((size.width - controlSize.width) / 2.0), y: floor((size.height - controlSize.height) / 2.0)), size: controlSize) + } +} diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index cc65fc5383..55e466dc6b 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -181,13 +181,9 @@ final class ThemeGridController: ViewController { if wallpaper == strongSelf.presentationData.chatWallpaper { let presentationData = strongSelf.presentationData let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in - var fallbackWallpaper = presentationData.theme.chat.defaultWallpaper - if case let .cloud(info) = current.theme, let resolvedWallpaper = info.resolvedWallpaper { - fallbackWallpaper = resolvedWallpaper - } var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers themeSpecificChatWallpapers[current.theme.index] = nil - return PresentationThemeSettings(chatWallpaper: fallbackWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) })).start() break } @@ -245,13 +241,9 @@ final class ThemeGridController: ViewController { } else { current = PresentationThemeSettings.defaultSettings } - var fallbackWallpaper = presentationData.theme.chat.defaultWallpaper - if case let .cloud(info) = current.theme, let resolvedWallpaper = info.resolvedWallpaper { - fallbackWallpaper = resolvedWallpaper - } var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers themeSpecificChatWallpapers[current.theme.index] = nil - return PresentationThemeSettings(chatWallpaper: fallbackWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: [:], fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: [:], fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) }).start() diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift index 2cc1a23405..387770511e 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift @@ -30,6 +30,12 @@ private func areWallpapersEqual(_ lhs: TelegramWallpaper, _ rhs: TelegramWallpap } else { return false } + case let .gradient(topColor, bottomColor): + if case .gradient(topColor, bottomColor) = rhs { + return true + } else { + return false + } case let .image(representations, _): if case .image(representations, _) = rhs { return true @@ -103,14 +109,18 @@ private struct ThemeGridControllerEntry: Comparable, Identifiable { return 0 case let .color(color): return (Int64(1) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: color))) + case let .gradient(topColor, bottomColor): + var hash: UInt32 = UInt32(bitPattern: topColor) + hash = hash &* 31 &+ UInt32(bitPattern: bottomColor) + return (Int64(2) << 32) | Int64(hash) case let .file(id, _, _, _, _, _, _, _, settings): var hash: Int = id.hashValue hash = hash &* 31 &+ (settings.color?.hashValue ?? 0) hash = hash &* 31 &+ (settings.intensity?.hashValue ?? 0) - return (Int64(2) << 32) | Int64(hash) + return (Int64(3) << 32) | Int64(hash) case let .image(representations, _): if let largest = largestImageRepresentation(representations) { - return (Int64(3) << 32) | Int64(largest.resource.id.hashValue) + return (Int64(4) << 32) | Int64(largest.resource.id.hashValue) } else { return 0 } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index cd724cf3f8..49d5c02141 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -289,17 +289,28 @@ public final class ThemePreviewController: ViewController { return .single(theme) } } - |> mapToSignal { theme -> Signal in - if case let .cloud(info) = theme { + |> mapToSignal { updatedTheme -> Signal in + if case let .cloud(info) = updatedTheme { let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: info.theme).start() let _ = saveThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme).start() } return context.sharedContext.accountManager.transaction { transaction -> Void in + let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in let current = entry as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers - themeSpecificChatWallpapers[theme.index] = nil - return PresentationThemeSettings(chatWallpaper: resolvedWallpaper ?? previewTheme.chat.defaultWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + themeSpecificChatWallpapers[updatedTheme.index] = nil + + var theme = current.theme + var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting + if autoNightModeTriggered { + automaticThemeSwitchSetting.theme = updatedTheme + } else { + theme = updatedTheme + } + + return PresentationThemeSettings(theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 55c6993aa5..59183ace82 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -436,7 +436,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The selectThemeImpl?(theme) }, selectFontSize: { size in let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in - return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: size, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: size, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }).start() }, openWallpaperSettings: { pushControllerImpl?(ThemeGridController(context: context)) @@ -452,30 +452,29 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The return current } + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers var themeSpecificAccentColors = current.themeSpecificAccentColors themeSpecificAccentColors[currentTheme.index] = color - var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers - var chatWallpaper = current.chatWallpaper + if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.hasWallpaper { } else { - chatWallpaper = theme.chat.defaultWallpaper - themeSpecificChatWallpapers[currentTheme.index] = chatWallpaper + themeSpecificChatWallpapers[currentTheme.index] = theme.chat.defaultWallpaper } - return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }).start() }, openAccentColorPicker: { themeReference, currentColor in - let controller = ThemeAccentColorController(context: context, currentTheme: themeReference, currentColor: currentColor?.color) + let controller = ThemeAccentColorController(context: context, currentTheme: themeReference, section: .accent) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }, openAutoNightTheme: { pushControllerImpl?(themeAutoNightSettingsController(context: context)) }, toggleLargeEmoji: { largeEmoji in let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in - return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: largeEmoji, disableAnimations: current.disableAnimations) }).start() }, disableAnimations: { value in let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in - return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: value) + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: value) }).start() }, selectAppIcon: { name in currentAppIconName.set(name) @@ -584,7 +583,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The let theme = presentationData.theme let accentColor = settings.themeSpecificAccentColors[themeReference.index]?.color - let wallpaper = settings.themeSpecificChatWallpapers[themeReference.index] ?? settings.chatWallpaper + let wallpaper = presentationData.chatWallpaper let rightNavigationButton = ItemListNavigationButton(content: .icon(.action), style: .regular, enabled: true, action: { moreImpl?() @@ -670,16 +669,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The } else { theme = updatedTheme } - - let chatWallpaper: TelegramWallpaper - if let themeSpecificWallpaper = current.themeSpecificChatWallpapers[updatedTheme.index] { - chatWallpaper = themeSpecificWallpaper - } else { - let presentationTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: updatedTheme, accentColor: current.themeSpecificAccentColors[updatedTheme.index]?.color, serviceBackgroundColor: .black, baseColor: nil) ?? defaultPresentationTheme - chatWallpaper = resolvedWallpaper ?? presentationTheme.chat.defaultWallpaper - } - - return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + + return PresentationThemeSettings(theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) }) }).start() diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift index 3e0a1dc81a..82951199bb 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift @@ -31,74 +31,83 @@ private func textInputBackgroundImage(fieldColor: UIColor, strokeColor: UIColor, } } -final class WallpaperColorPanelNode: ASDisplayNode, UITextFieldDelegate { +private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { private var theme: PresentationTheme - private let backgroundNode: ASDisplayNode - private let topSeparatorNode: ASDisplayNode - private let bottomSeparatorNode: ASDisplayNode + private let swatchNode: ASDisplayNode + private let removeButton: HighlightableButtonNode private let textBackgroundNode: ASImageNode - private let textFieldNode: TextFieldNode + private let selectionNode: ASDisplayNode + let textFieldNode: TextFieldNode + private let measureNode: ImmediateTextNode private let prefixNode: ASTextNode - private let doneButton: HighlightableButtonNode - private let colorPickerNode: WallpaperColorPickerNode - var previousColor: UIColor? - var color: UIColor { - get { - return self.colorPickerNode.color - } - set { - self.setColor(newValue) + private var validLayout: CGSize? + + private var previousColor: UIColor? + + var colorChanged: ((UIColor, Bool) -> Void)? + var colorRemoved: (() -> Void)? + var colorSelected: (() -> Void)? + + var color: UIColor = .white { + didSet { + self.setColor(self.color, update: false) } } - - var colorChanged: ((UIColor, Bool) -> Void)? - - init(theme: PresentationTheme, strings: PresentationStrings) { + + var isRemovable: Bool = false { + didSet { + self.removeButton.isUserInteractionEnabled = self.isRemovable + } + } + + var isSelected: Bool = false { + didSet { + self.selectionNode.isHidden = !self.isSelected + if !self.isSelected { + self.textFieldNode.textField.resignFirstResponder() + } + } + } + + init(theme: PresentationTheme) { self.theme = theme - self.backgroundNode = ASDisplayNode() - self.backgroundNode.backgroundColor = theme.chat.inputPanel.panelBackgroundColor - - self.topSeparatorNode = ASDisplayNode() - self.topSeparatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor - self.bottomSeparatorNode = ASDisplayNode() - self.bottomSeparatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor - self.textBackgroundNode = ASImageNode() self.textBackgroundNode.image = textInputBackgroundImage(fieldColor: theme.chat.inputPanel.inputBackgroundColor, strokeColor: theme.chat.inputPanel.inputStrokeColor, diameter: 33.0) self.textBackgroundNode.displayWithoutProcessing = true self.textBackgroundNode.displaysAsynchronously = false + self.selectionNode = ASDisplayNode() + self.selectionNode.backgroundColor = theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.2) + self.selectionNode.cornerRadius = 3.0 + self.selectionNode.isUserInteractionEnabled = false + self.textFieldNode = TextFieldNode() + self.measureNode = ImmediateTextNode() + self.prefixNode = ASTextNode() self.prefixNode.attributedText = NSAttributedString(string: "#", font: Font.regular(17.0), textColor: self.theme.chat.inputPanel.inputTextColor) - - self.doneButton = HighlightableButtonNode() - self.doneButton.setImage(PresentationResourcesChat.chatInputPanelApplyButtonImage(theme), for: .normal) - self.colorPickerNode = WallpaperColorPickerNode(strings: strings) - + self.swatchNode = ASDisplayNode() + self.swatchNode.cornerRadius = 10.5 + + self.removeButton = HighlightableButtonNode() + self.removeButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorRemoveIcon"), color: theme.chat.inputPanel.inputControlColor), for: .normal) + super.init() - self.addSubnode(self.backgroundNode) - self.addSubnode(self.topSeparatorNode) - self.addSubnode(self.bottomSeparatorNode) self.addSubnode(self.textBackgroundNode) + self.addSubnode(self.selectionNode) self.addSubnode(self.textFieldNode) self.addSubnode(self.prefixNode) - self.addSubnode(self.doneButton) - self.addSubnode(self.colorPickerNode) + self.addSubnode(self.swatchNode) + self.addSubnode(self.removeButton) - self.colorPickerNode.colorChanged = { [weak self] color in - self?.setColor(color, updatePicker: false, ended: false) - } - self.colorPickerNode.colorChangeEnded = { [weak self] color in - self?.setColor(color, updatePicker: false, ended: true) - } + self.removeButton.addTarget(self, action: #selector(self.removePressed), forControlEvents: .touchUpInside) } - + override func didLoad() { super.didLoad() @@ -117,53 +126,41 @@ final class WallpaperColorPanelNode: ASDisplayNode, UITextFieldDelegate { func updateTheme(_ theme: PresentationTheme) { self.theme = theme - self.backgroundNode.backgroundColor = self.theme.chat.inputPanel.panelBackgroundColor - self.topSeparatorNode.backgroundColor = self.theme.chat.inputPanel.panelSeparatorColor - self.bottomSeparatorNode.backgroundColor = self.theme.chat.inputPanel.panelSeparatorColor + self.textBackgroundNode.image = textInputBackgroundImage(fieldColor: self.theme.chat.inputPanel.inputBackgroundColor, strokeColor: self.theme.chat.inputPanel.inputStrokeColor, diameter: 33.0) self.textFieldNode.textField.textColor = self.theme.chat.inputPanel.inputTextColor self.textFieldNode.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance self.textFieldNode.textField.tintColor = self.theme.list.itemAccentColor + + self.selectionNode.backgroundColor = theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.2) } - private func setColor(_ color: UIColor, updatePicker: Bool = true, ended: Bool = true) { - self.textFieldNode.textField.text = color.hexString.uppercased() - if updatePicker { - self.colorPickerNode.color = color + func setColor(_ color: UIColor, update: Bool = true, ended: Bool = true) { + let text = color.hexString.uppercased() + self.textFieldNode.textField.text = text + if let size = self.validLayout { + self.updateSelectionLayout(size: size, transition: .immediate) } - self.colorChanged?(color, ended) + if update { + self.colorChanged?(color, ended) + } + self.swatchNode.backgroundColor = color } - func updateLayout(size: CGSize, keyboardHeight: CGFloat, transition: ContainedViewLayoutTransition) { - let separatorHeight = UIScreenPixel - let topPanelHeight: CGFloat = 47.0 - transition.updateFrame(node: self.backgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: topPanelHeight)) - transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: separatorHeight)) - transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(x: 0.0, y: topPanelHeight, width: size.width, height: separatorHeight)) - - let fieldHeight: CGFloat = 33.0 - let buttonSpacing: CGFloat = keyboardHeight > 0.0 ? 3.0 : 6.0 - let leftInset: CGFloat = 5.0 - let rightInset: CGFloat = 5.0 - - transition.updateFrame(node: self.textBackgroundNode, frame: CGRect(x: leftInset, y: (topPanelHeight - fieldHeight) / 2.0, width: size.width - leftInset - rightInset, height: fieldHeight)) - transition.updateFrame(node: self.textFieldNode, frame: CGRect(x: leftInset + 24.0, y: (topPanelHeight - fieldHeight) / 2.0 + 1.0, width: size.width - leftInset - rightInset - 36.0, height: fieldHeight - 2.0)) - - let prefixSize = self.prefixNode.measure(CGSize(width: size.width, height: fieldHeight)) - transition.updateFrame(node: self.prefixNode, frame: CGRect(origin: CGPoint(x: leftInset + 13.0, y: 12.0 + UIScreenPixel), size: prefixSize)) - transition.updateFrame(node: self.doneButton, frame: CGRect(x: 0.0, y: size.width - rightInset + buttonSpacing, width: topPanelHeight, height: topPanelHeight)) - - let colorPickerSize = CGSize(width: size.width, height: size.height - topPanelHeight - separatorHeight) - transition.updateFrame(node: self.colorPickerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + separatorHeight), size: colorPickerSize)) - self.colorPickerNode.updateLayout(size: colorPickerSize, transition: transition) + @objc private func removePressed() { + self.colorRemoved?() } - + @objc internal func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { var updated = textField.text ?? "" updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string) if updated.count <= 6 && updated.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil { textField.text = updated.uppercased() + + if let size = self.validLayout { + self.updateSelectionLayout(size: size, transition: .immediate) + } } return false } @@ -172,6 +169,10 @@ final class WallpaperColorPanelNode: ASDisplayNode, UITextFieldDelegate { if let text = sender.text, text.count == 6, let color = UIColor(hexString: text) { self.setColor(color) } + + if let size = self.validLayout { + self.updateSelectionLayout(size: size, transition: .immediate) + } } @objc func textFieldShouldReturn(_ textField: UITextField) -> Bool { @@ -180,8 +181,13 @@ final class WallpaperColorPanelNode: ASDisplayNode, UITextFieldDelegate { } func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - self.previousColor = self.color - return true + if self.isSelected { + self.previousColor = self.color + return true + } else { + self.colorSelected?() + return false + } } @objc func textFieldDidEndEditing(_ textField: UITextField) { @@ -191,4 +197,314 @@ final class WallpaperColorPanelNode: ASDisplayNode, UITextFieldDelegate { self.setColor(self.previousColor ?? .black) } } + + private func updateSelectionLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + self.measureNode.attributedText = NSAttributedString(string: self.textFieldNode.textField.text ?? "", font: self.textFieldNode.textField.font) + let size = self.measureNode.updateLayout(size) + transition.updateFrame(node: self.selectionNode, frame: CGRect(x: 47.0, y: 6.0, width: max(45.0, size.width), height: 20.0)) + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + self.validLayout = size + + transition.updateFrame(node: self.swatchNode, frame: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 21.0, height: 21.0))) + + transition.updateFrame(node: self.textBackgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) + transition.updateFrame(node: self.textFieldNode, frame: CGRect(x: 47.0, y: 1.0, width: size.width - 61.0, height: size.height - 2.0)) + + self.updateSelectionLayout(size: size, transition: transition) + + let prefixSize = self.prefixNode.measure(size) + transition.updateFrame(node: self.prefixNode, frame: CGRect(origin: CGPoint(x: 37.0 - UIScreenPixel, y: 6.0), size: prefixSize)) + + let removeSize = CGSize(width: 33.0, height: 33.0) + transition.updateFrame(node: self.removeButton, frame: CGRect(origin: CGPoint(x: size.width - removeSize.width, y: 0.0), size: removeSize)) + transition.updateAlpha(node: self.removeButton, alpha: self.isRemovable ? 1.0 : 0.0) + } +} + +enum WallpaperColorPanelNodeSelectionState { + case none + case first + case second +} + +struct WallpaperColorPanelNodeState { + var selection: WallpaperColorPanelNodeSelectionState + var firstColor: UIColor + var firstColorRemovable: Bool + var secondColor: UIColor? + var secondColorAvailable: Bool +} + +final class WallpaperColorPanelNode: ASDisplayNode { + private var theme: PresentationTheme + + private var state: WallpaperColorPanelNodeState + + private let backgroundNode: ASDisplayNode + private let topSeparatorNode: ASDisplayNode + private let bottomSeparatorNode: ASDisplayNode + private let firstColorFieldNode: ColorInputFieldNode + private let secondColorFieldNode: ColorInputFieldNode + private let swapButton: HighlightableButtonNode + private let addButton: HighlightableButtonNode + private let doneButton: HighlightableButtonNode + private let colorPickerNode: WallpaperColorPickerNode + + var colorsChanged: ((UIColor, UIColor?, Bool) -> Void)? + + private var validLayout: (CGSize, CGFloat)? + + init(theme: PresentationTheme, strings: PresentationStrings) { + self.theme = theme + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = theme.chat.inputPanel.panelBackgroundColor + + self.topSeparatorNode = ASDisplayNode() + self.topSeparatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor + self.bottomSeparatorNode = ASDisplayNode() + self.bottomSeparatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor + + self.doneButton = HighlightableButtonNode() + self.doneButton.setImage(PresentationResourcesChat.chatInputPanelApplyButtonImage(theme), for: .normal) + + self.colorPickerNode = WallpaperColorPickerNode(strings: strings) + + self.swapButton = HighlightableButtonNode() + self.swapButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorSwapIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal) + self.addButton = HighlightableButtonNode() + self.addButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorAddIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal) + + self.firstColorFieldNode = ColorInputFieldNode(theme: theme) + self.secondColorFieldNode = ColorInputFieldNode(theme: theme) + + self.state = WallpaperColorPanelNodeState(selection: .first, firstColor: .white, firstColorRemovable: false, secondColor: nil, secondColorAvailable: true) + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.topSeparatorNode) + self.addSubnode(self.bottomSeparatorNode) + self.addSubnode(self.firstColorFieldNode) + self.addSubnode(self.secondColorFieldNode) + self.addSubnode(self.doneButton) + self.addSubnode(self.colorPickerNode) + + self.addSubnode(self.swapButton) + self.addSubnode(self.addButton) + + self.swapButton.addTarget(self, action: #selector(self.swapPressed), forControlEvents: .touchUpInside) + self.addButton.addTarget(self, action: #selector(self.addPressed), forControlEvents: .touchUpInside) + + self.firstColorFieldNode.colorChanged = { [weak self] color, ended in + if let strongSelf = self { + strongSelf.updateState({ current in + var updated = current + updated.firstColor = color + return updated + }) + } + } + self.firstColorFieldNode.colorRemoved = { [weak self] in + if let strongSelf = self { + strongSelf.updateState({ current in + var updated = current + updated.selection = .first + updated.firstColor = updated.secondColor ?? updated.firstColor + updated.secondColor = nil + return updated + }) + } + } + self.firstColorFieldNode.colorSelected = { [weak self] in + if let strongSelf = self { + strongSelf.updateState({ current in + var updated = current + updated.selection = .first + return updated + }) + } + } + + self.secondColorFieldNode.colorChanged = { [weak self] color, ended in + if let strongSelf = self { + strongSelf.updateState({ current in + var updated = current + updated.secondColor = color + return updated + }) + } + } + self.secondColorFieldNode.colorRemoved = { [weak self] in + if let strongSelf = self { + strongSelf.updateState({ current in + var updated = current + updated.selection = .first + updated.secondColor = nil + return updated + }) + } + } + self.secondColorFieldNode.colorSelected = { [weak self] in + if let strongSelf = self { + strongSelf.updateState({ current in + var updated = current + updated.selection = .second + return updated + }) + } + } + + self.colorPickerNode.colorChanged = { [weak self] color in + if let strongSelf = self { + strongSelf.updateState({ current in + var updated = current + switch strongSelf.state.selection { + case .first: + updated.firstColor = color + case .second: + updated.secondColor = color + default: + break + } + return updated + }, updateLayout: false) + } + } + self.colorPickerNode.colorChangeEnded = { [weak self] color in + if let strongSelf = self { + strongSelf.updateState({ current in + var updated = current + switch strongSelf.state.selection { + case .first: + updated.firstColor = color + case .second: + updated.secondColor = color + default: + break + } + return updated + }, updateLayout: true) + } + } + } + + func updateTheme(_ theme: PresentationTheme) { + self.theme = theme + self.backgroundNode.backgroundColor = self.theme.chat.inputPanel.panelBackgroundColor + self.topSeparatorNode.backgroundColor = self.theme.chat.inputPanel.panelSeparatorColor + self.bottomSeparatorNode.backgroundColor = self.theme.chat.inputPanel.panelSeparatorColor + self.firstColorFieldNode.updateTheme(theme) + self.secondColorFieldNode.updateTheme(theme) + } + + func updateState(_ f: (WallpaperColorPanelNodeState) -> WallpaperColorPanelNodeState, updateLayout: Bool = true, animated: Bool = true) { + let firstColor = self.state.firstColor.rgb + let secondColor = self.state.secondColor?.rgb + self.state = f(self.state) + + self.firstColorFieldNode.setColor(self.state.firstColor, update: false) + if let secondColor = self.state.secondColor { + self.secondColorFieldNode.setColor(secondColor, update: false) + } + + if updateLayout, let (size, keyboardHeight) = self.validLayout { + switch self.state.selection { + case .first: + self.colorPickerNode.color = self.state.firstColor + case .second: + if let secondColor = self.state.secondColor { + self.colorPickerNode.color = secondColor + } + default: + break + } + + self.updateLayout(size: size, keyboardHeight: keyboardHeight, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate) + } + + if self.state.firstColor.rgb != firstColor || self.state.secondColor?.rgb != secondColor { + self.colorsChanged?(self.state.firstColor, self.state.secondColor, updateLayout) + } + } + + func updateLayout(size: CGSize, keyboardHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, keyboardHeight) + + let separatorHeight = UIScreenPixel + let topPanelHeight: CGFloat = 47.0 + transition.updateFrame(node: self.backgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: topPanelHeight)) + transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: separatorHeight)) + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(x: 0.0, y: topPanelHeight, width: size.width, height: separatorHeight)) + + let fieldHeight: CGFloat = 33.0 + let buttonSpacing: CGFloat = keyboardHeight > 0.0 ? 3.0 : 6.0 + let leftInset: CGFloat = 15.0 + let rightInset: CGFloat = 15.0 + let rightInsetWithButton: CGFloat = 42.0 + let fieldSpacing: CGFloat = 45.0 + + let buttonSize = CGSize(width: 26.0, height: 26.0) + let buttonOffset: CGFloat = (rightInsetWithButton - 13.0) / 2.0 + let swapButtonFrame = CGRect(origin: CGPoint(x: self.state.secondColor != nil ? floor((size.width - 26.0) / 2.0) : (self.state.secondColorAvailable ? size.width - rightInsetWithButton + floor((rightInsetWithButton - buttonSize.width) / 2.0) : size.width + buttonOffset), y: floor((topPanelHeight - buttonSize.height) / 2.0)), size: buttonSize) + + transition.updateFrame(node: self.swapButton, frame: swapButtonFrame) + transition.updateFrame(node: self.addButton, frame: swapButtonFrame) + + let swapButtonAlpha: CGFloat + let addButtonAlpha: CGFloat + if let _ = self.state.secondColor { + swapButtonAlpha = 1.0 + addButtonAlpha = 0.0 + } else { + swapButtonAlpha = 0.0 + if self.state.secondColorAvailable { + addButtonAlpha = 1.0 + } else { + addButtonAlpha = 0.0 + } + } + transition.updateAlpha(node: self.swapButton, alpha: swapButtonAlpha) + transition.updateAlpha(node: self.addButton, alpha: addButtonAlpha) + + self.firstColorFieldNode.isRemovable = self.state.secondColor != nil || self.state.firstColorRemovable + self.secondColorFieldNode.isRemovable = true + + self.firstColorFieldNode.isSelected = self.state.selection == .first + self.secondColorFieldNode.isSelected = self.state.selection == .second + + let firstFieldFrame = CGRect(x: leftInset, y: (topPanelHeight - fieldHeight) / 2.0, width: self.state.secondColor != nil ? floorToScreenPixels((size.width - fieldSpacing) / 2.0) - leftInset : size.width - leftInset - (self.state.secondColorAvailable ? rightInsetWithButton : rightInset), height: fieldHeight) + transition.updateFrame(node: self.firstColorFieldNode, frame: firstFieldFrame) + self.firstColorFieldNode.updateLayout(size: firstFieldFrame.size, transition: transition) + + let secondFieldFrame = CGRect(x: firstFieldFrame.maxX + fieldSpacing, y: (topPanelHeight - fieldHeight) / 2.0, width: firstFieldFrame.width, height: fieldHeight) + transition.updateFrame(node: self.secondColorFieldNode, frame: secondFieldFrame) + self.secondColorFieldNode.updateLayout(size: secondFieldFrame.size, transition: transition) + + let colorPickerSize = CGSize(width: size.width, height: size.height - topPanelHeight - separatorHeight) + transition.updateFrame(node: self.colorPickerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + separatorHeight), size: colorPickerSize)) + self.colorPickerNode.updateLayout(size: colorPickerSize, transition: transition) + } + + @objc private func swapPressed() { + self.updateState({ current in + var updated = current + if let secondColor = current.secondColor { + updated.firstColor = secondColor + updated.secondColor = current.firstColor + } + return updated + }) + } + + @objc private func addPressed() { + self.updateState({ current in + var updated = current + updated.selection = .second + updated.secondColor = current.firstColor + return updated + }) + } } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift index c0ebb29a7d..f87fef8323 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift @@ -18,7 +18,7 @@ private let shadowImage: UIImage = { }() private let pointerImage: UIImage = { - return generateImage(CGSize(width: 12.0, height: 42.0), opaque: false, scale: nil, rotatedContext: { size, context in + return generateImage(CGSize(width: 12.0, height: 55.0), opaque: false, scale: nil, rotatedContext: { size, context in context.setBlendMode(.clear) context.setFillColor(UIColor.clear.cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) @@ -29,8 +29,9 @@ private let pointerImage: UIImage = { context.setStrokeColor(UIColor.white.cgColor) context.setLineWidth(lineWidth) context.setLineCap(.round) + context.setLineJoin(.round) - let pointerHeight: CGFloat = 6.0 + let pointerHeight: CGFloat = 7.0 context.move(to: CGPoint(x: lineWidth / 2.0, y: lineWidth / 2.0)) context.addLine(to: CGPoint(x: size.width - lineWidth / 2.0, y: lineWidth / 2.0)) context.addLine(to: CGPoint(x: size.width / 2.0, y: lineWidth / 2.0 + pointerHeight)) @@ -100,7 +101,7 @@ private final class WallpaperColorKnobNode: ASDisplayNode { let color = UIColor(hue: parameters.hue, saturation: parameters.saturation, brightness: parameters.value, alpha: 1.0) context.setFillColor(color.cgColor) - let borderWidth: CGFloat = bounds.width > 30.0 ? 5.0 : 5.0 + let borderWidth: CGFloat = 7.0 context.fillEllipse(in: bounds.insetBy(dx: borderWidth - UIScreenPixel, dy: borderWidth - UIScreenPixel)) } } @@ -265,11 +266,11 @@ final class WallpaperColorPickerNode: ASDisplayNode { self.colorKnobNode.hsv = self.colorHSV } - func updateKnobLayout(size: CGSize, panningColor: Bool, transition: ContainedViewLayoutTransition) { + private func updateKnobLayout(size: CGSize, panningColor: Bool, transition: ContainedViewLayoutTransition) { let knobSize = CGSize(width: 45.0, height: 45.0) let colorHeight = size.height - 66.0 - var colorKnobFrame = CGRect(x: -knobSize.width / 2.0 + size.width * self.colorHSV.0, y: -knobSize.height / 2.0 + (colorHeight * (1.0 - self.colorHSV.1)), width: knobSize.width, height: knobSize.height) + var colorKnobFrame = CGRect(x: floorToScreenPixels(-knobSize.width / 2.0 + size.width * self.colorHSV.0), y: floorToScreenPixels(-knobSize.height / 2.0 + (colorHeight * (1.0 - self.colorHSV.1))), width: knobSize.width, height: knobSize.height) var origin = colorKnobFrame.origin if !panningColor { origin = CGPoint(x: max(0.0, min(origin.x, size.width - knobSize.width)), y: max(0.0, min(origin.y, colorHeight - knobSize.height))) @@ -280,8 +281,8 @@ final class WallpaperColorPickerNode: ASDisplayNode { transition.updateFrame(node: self.colorKnobNode, frame: colorKnobFrame) let inset: CGFloat = 42.0 - let brightnessKnobSize = CGSize(width: 12.0, height: 42.0) - let brightnessKnobFrame = CGRect(x: inset - brightnessKnobSize.width / 2.0 + (size.width - inset * 2.0) * (1.0 - self.colorHSV.2), y: size.height - 61.0, width: brightnessKnobSize.width, height: brightnessKnobSize.height) + let brightnessKnobSize = CGSize(width: 12.0, height: 55.0) + let brightnessKnobFrame = CGRect(x: inset - brightnessKnobSize.width / 2.0 + (size.width - inset * 2.0) * (1.0 - self.colorHSV.2), y: size.height - 65.0, width: brightnessKnobSize.width, height: brightnessKnobSize.height) transition.updateFrame(node: self.brightnessKnobNode, frame: brightnessKnobFrame) } @@ -291,8 +292,8 @@ final class WallpaperColorPickerNode: ASDisplayNode { let colorHeight = size.height - 66.0 transition.updateFrame(node: self.colorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: colorHeight)) - let inset: CGFloat = 42.0 - transition.updateFrame(node: self.brightnessNode, frame: CGRect(x: inset, y: size.height - 55.0, width: size.width - inset * 2.0, height: 29.0)) + let inset: CGFloat = 15.0 + transition.updateFrame(node: self.brightnessNode, frame: CGRect(x: inset, y: size.height - 55.0, width: size.width - inset * 2.0, height: 35.0)) self.updateKnobLayout(size: size, panningColor: false, transition: .immediate) } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift index f3304f3499..419514898b 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift @@ -341,13 +341,13 @@ public class WallpaperGalleryController: ViewController { self.galleryNode.addSubnode(overlayNode) let colorPanelNode = WallpaperColorPanelNode(theme: presentationData.theme, strings: presentationData.strings) - colorPanelNode.colorChanged = { [weak self] color, ended in + colorPanelNode.colorsChanged = { [weak self] color, _, ended in if let strongSelf = self { strongSelf.updateEntries(color: color, preview: !ended) } } if case let .customColor(colorValue) = self.source, let color = colorValue { - colorPanelNode.color = UIColor(rgb: UInt32(bitPattern: color)) + //colorPanelNode.color = UIColor(rgb: UInt32(bitPattern: color)) } self.colorPanelNode = colorPanelNode overlayNode.addSubnode(colorPanelNode) @@ -387,15 +387,12 @@ public class WallpaperGalleryController: ViewController { let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers - var chatWallpaper = current.chatWallpaper if autoNightModeTriggered { themeSpecificChatWallpapers[current.automaticThemeSwitchSetting.theme.index] = wallpaper } else { themeSpecificChatWallpapers[current.theme.index] = wallpaper - chatWallpaper = wallpaper } - - return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) |> deliverOnMainQueue).start(completed: { self?.dismiss(forceAway: true) }) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 16c46210e4..a923693a8e 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -226,14 +226,23 @@ final class WallpaperGalleryItemNode: GalleryItemNode { case let .color(color): displaySize = CGSize(width: 1.0, height: 1.0) contentSize = displaySize - signal = solidColor(UIColor(rgb: UInt32(bitPattern: color))) + signal = solidColorImage(UIColor(rgb: UInt32(bitPattern: color))) + fetchSignal = .complete() + statusSignal = .single(.Local) + subtitleSignal = .single(nil) + actionSignal = .single(defaultAction) + colorSignal = chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: self.context.account.postbox.mediaBox) + isBlurrable = false + case let .gradient(topColor, bottomColor): + displaySize = CGSize(width: 1.0, height: 1.0) + contentSize = displaySize + signal = gradientImage([UIColor(rgb: UInt32(bitPattern: topColor)), UIColor(rgb: UInt32(bitPattern: bottomColor))]) fetchSignal = .complete() statusSignal = .single(.Local) subtitleSignal = .single(nil) actionSignal = .single(defaultAction) colorSignal = chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: self.context.account.postbox.mediaBox) isBlurrable = false - //self.backgroundColor = UIColor(rgb: UInt32(bitPattern: color)) case let .file(file): let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100) contentSize = dimensions.cgSize @@ -715,6 +724,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode { blurFrame = leftButtonFrame motionAlpha = 1.0 motionFrame = rightButtonFrame + case .gradient: + blurAlpha = 0.0 + patternAlpha = 0.0 + motionAlpha = 0.0 case let .file(file): if file.isPattern { motionAlpha = 1.0 diff --git a/submodules/SyncCore/Sources/TelegramWallpaper.swift b/submodules/SyncCore/Sources/TelegramWallpaper.swift index da472ffd7a..a7876706c1 100644 --- a/submodules/SyncCore/Sources/TelegramWallpaper.swift +++ b/submodules/SyncCore/Sources/TelegramWallpaper.swift @@ -39,6 +39,7 @@ public struct WallpaperSettings: PostboxCoding, Equatable { public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable { case builtin(WallpaperSettings) case color(Int32) + case gradient(Int32, Int32) case image([TelegramMediaImageRepresentation], WallpaperSettings) case file(id: Int64, accessHash: Int64, isCreator: Bool, isDefault: Bool, isPattern: Bool, isDark: Bool, slug: String, file: TelegramMediaFile, settings: WallpaperSettings) @@ -59,7 +60,8 @@ public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable { } else { self = .color(0xffffff) } - + case 4: + self = .gradient(decoder.decodeInt32ForKey("c1", orElse: 0), decoder.decodeInt32ForKey("c2", orElse: 0)) default: assertionFailure() self = .color(0xffffff) @@ -83,6 +85,10 @@ public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable { case let .color(color): encoder.encodeInt32(1, forKey: "v") encoder.encodeInt32(color, forKey: "c") + case let .gradient(topColor, bottomColor): + encoder.encodeInt32(4, forKey: "v") + encoder.encodeInt32(topColor, forKey: "c1") + encoder.encodeInt32(bottomColor, forKey: "c2") case let .image(representations, settings): encoder.encodeInt32(2, forKey: "v") encoder.encodeObjectArray(representations, forKey: "i") @@ -115,6 +121,12 @@ public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable { } else { return false } + case let .gradient(topColor, bottomColor): + if case .gradient(topColor, bottomColor) = rhs { + return true + } else { + return false + } case let .image(representations, settings): if case .image(representations, settings) = rhs { return true @@ -145,6 +157,8 @@ public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable { return .builtin(settings) case .color: return self + case .gradient: + return self case let .image(representations, _): return .image(representations, settings) case let .file(id, accessHash, isCreator, isDefault, isPattern, isDark, slug, file, _): diff --git a/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift b/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift index 5375ddab1b..e88b0c96cc 100644 --- a/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift +++ b/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift @@ -41,6 +41,16 @@ public func chatControllerBackgroundImage(theme: PresentationTheme, wallpaper in context.setFillColor(UIColor(rgb: UInt32(bitPattern: color)).cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) }) + case let .gradient(topColor, bottomColor): + backgroundImage = generateImage(CGSize(width: 1.0, height: 1280.0), rotatedContext: { size, context in + let gradientColors = [UIColor(rgb: UInt32(bitPattern: topColor)).cgColor, UIColor(rgb: UInt32(bitPattern: bottomColor)).cgColor] as CFArray + + var locations: [CGFloat] = [0.0, 1.0] + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + }) case let .image(representations, settings): if let largest = largestImageRepresentation(representations) { if settings.blur && composed { diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index ffeb2c0335..9ed62b2d6c 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -228,11 +228,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager, s let contactSettings: ContactSynchronizationSettings = (transaction.getSharedData(ApplicationSpecificSharedDataKeys.contactSynchronizationSettings) as? ContactSynchronizationSettings) ?? ContactSynchronizationSettings.defaultSettings - let themeValue: PresentationTheme - let effectiveTheme: PresentationThemeReference - var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper - let parameters = AutomaticThemeSwitchParameters(settings: themeSettings.automaticThemeSwitchSetting) let autoNightModeTriggered: Bool if automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: systemUserInterfaceStyle) { @@ -244,16 +240,8 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager, s } let effectiveAccentColor = themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.color - themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveAccentColor, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.baseColor ?? .blue) ?? defaultPresentationTheme - - if effectiveTheme != themeSettings.theme { - switch effectiveChatWallpaper { - case .builtin, .color: - effectiveChatWallpaper = themeValue.chat.defaultWallpaper - default: - break - } - } + let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveAccentColor, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.baseColor ?? .blue) ?? defaultPresentationTheme + let effectiveChatWallpaper: TelegramWallpaper = themeSettings.themeSpecificChatWallpapers[effectiveTheme.index] ?? theme.chat.defaultWallpaper let dateTimeFormat = currentDateTimeFormat() let stringsValue: PresentationStrings @@ -264,7 +252,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager, s } let nameDisplayOrder = contactSettings.nameDisplayOrder let nameSortOrder = currentPersonNameSortOrder() - return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: themeValue, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations, largeEmoji: themeSettings.largeEmoji), automaticMediaDownloadSettings: automaticMediaDownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, experimentalUISettings: experimentalUISettings) + return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: theme, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations, largeEmoji: themeSettings.largeEmoji), automaticMediaDownloadSettings: automaticMediaDownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, experimentalUISettings: experimentalUISettings) } } @@ -371,17 +359,21 @@ private func serviceColor(for data: Signal) -> Signa } } +public func averageColor(from image: UIImage) -> UIColor { + let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false) + context.withFlippedContext({ context in + if let cgImage = image.cgImage { + context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)) + } + }) + return context.colorAt(CGPoint()) +} + public func serviceColor(from image: Signal) -> Signal { return image |> mapToSignal { image -> Signal in if let image = image { - let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false) - context.withFlippedContext({ context in - if let cgImage = image.cgImage { - context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)) - } - }) - return .single(serviceColor(with: context.colorAt(CGPoint()))) + return .single(serviceColor(with: averageColor(from: image))) } return .complete() } @@ -414,6 +406,9 @@ public func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, mediaBox: M return .single(UIColor(rgb: 0x748391, alpha: 0.45)) case let .color(color): return .single(serviceColor(with: UIColor(rgb: UInt32(bitPattern: color)))) + case let .gradient(topColor, bottomColor): + let mixedColor = UIColor(rgb: UInt32(bitPattern: topColor)).mixedWith(UIColor(rgb: UInt32(bitPattern: bottomColor)), alpha: 0.5) + return .single(serviceColor(with: mixedColor)) case let .image(representations, _): if let largest = largestImageRepresentation(representations) { return Signal { subscriber in @@ -476,10 +471,11 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI if let themeSpecificWallpaper = themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index] { currentWallpaper = themeSpecificWallpaper } else { - currentWallpaper = themeSettings.chatWallpaper + let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: nil, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: nil) ?? defaultPresentationTheme + currentWallpaper = theme.chat.defaultWallpaper } - return (.single(UIColor(rgb: 0x000000, alpha: 0.3)) + return (.single(defaultServiceBackgroundColor) |> then(chatServiceBackgroundColor(wallpaper: currentWallpaper, mediaBox: accountManager.mediaBox))) |> mapToSignal { serviceBackgroundColor in return applicationInForeground diff --git a/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorAddIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorAddIcon.imageset/Contents.json new file mode 100644 index 0000000000..02e013a01c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorAddIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_input_add.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorAddIcon.imageset/ic_input_add.pdf b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorAddIcon.imageset/ic_input_add.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8c29c43c791f95b750e2e037f306184a8efc6bab GIT binary patch literal 3979 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 C8|Cx> literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/Contents.json new file mode 100644 index 0000000000..8dec65adad --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_input_change.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/ic_input_change.pdf b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/ic_input_change.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6151d0db8b2a6465012ae0487ec5a59ee9b77cbb GIT binary patch literal 4296 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/Contents.json deleted file mode 100644 index 5426142806..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "color@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "color@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/color@2x.png b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/color@2x.png deleted file mode 100644 index ff0220da25a9d398b7ec625b9a954dfc5429a94d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2486 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 P00000NkvXXu0mjfLkpt< diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift index fe019dd5d6..9066975417 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -580,7 +580,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } case let .color(color): - return solidColor(color) + return solidColorImage(color) } } diff --git a/submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift b/submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift index a6c3d8f9e8..10e531c358 100644 --- a/submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift +++ b/submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift @@ -131,25 +131,15 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager { current = PresentationThemeSettings.defaultSettings } - var chatWallpaper = current.chatWallpaper + var theme = current.theme var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting if isAutoNight { automaticThemeSwitchSetting.theme = updatedTheme } else { - if let themeSpecificWallpaper = current.themeSpecificChatWallpapers[updatedTheme.index] { - chatWallpaper = themeSpecificWallpaper - } else if let presentationTheme = presentationTheme { - if case let .cloud(info) = updatedTheme, let resolvedWallpaper = info.resolvedWallpaper { - chatWallpaper = resolvedWallpaper - } else { - chatWallpaper = presentationTheme.chat.defaultWallpaper - } - } else { - chatWallpaper = current.chatWallpaper - } + theme = updatedTheme } - return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: updatedTheme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) }).start() } diff --git a/submodules/TelegramUI/TelegramUI/UpgradedAccounts.swift b/submodules/TelegramUI/TelegramUI/UpgradedAccounts.swift index 72f5efb54f..5eedd94ae5 100644 --- a/submodules/TelegramUI/TelegramUI/UpgradedAccounts.swift +++ b/submodules/TelegramUI/TelegramUI/UpgradedAccounts.swift @@ -158,7 +158,7 @@ public func upgradedAccounts(accountManager: AccountManager, rootPath: String, e if let value = values[LegacyApplicationSpecificPreferencesKeyValues.presentationThemeSettings.key] as? PresentationThemeSettings { let mediaBox = MediaBox(basePath: path + "/postbox/media") - let wallpapers = [value.chatWallpaper] + Array(value.themeSpecificChatWallpapers.values) + let wallpapers = Array(value.themeSpecificChatWallpapers.values) for wallpaper in wallpapers { switch wallpaper { case let .file(file): diff --git a/submodules/TelegramUI/TelegramUI/WallpaperUploadManager.swift b/submodules/TelegramUI/TelegramUI/WallpaperUploadManager.swift index 8bbdb76129..24147f13b1 100644 --- a/submodules/TelegramUI/TelegramUI/WallpaperUploadManager.swift +++ b/submodules/TelegramUI/TelegramUI/WallpaperUploadManager.swift @@ -119,7 +119,7 @@ final class WallpaperUploadManagerImpl: WallpaperUploadManager { if strongSelf.currentPresentationData?.theme.name == presentationData.theme.name { let _ = (updatePresentationThemeSettingsInteractively(accountManager: sharedContext.accountManager, { current in let updatedWallpaper: TelegramWallpaper - if let currentSettings = current.chatWallpaper.settings { + if let currentSettings = currentWallpaper.settings { updatedWallpaper = wallpaper.withUpdatedSettings(currentSettings) } else { updatedWallpaper = wallpaper @@ -127,7 +127,7 @@ final class WallpaperUploadManagerImpl: WallpaperUploadManager { var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers themeSpecificChatWallpapers[current.theme.index] = updatedWallpaper - return PresentationThemeSettings(chatWallpaper: updatedWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificBubbleColors: current.themeSpecificBubbleColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) })).start() } } diff --git a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift index d7d1de1c33..71a7b9c8b3 100644 --- a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift @@ -376,6 +376,38 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable { } } +public struct PresentationThemeColorPair: PostboxCoding, Equatable { + public var color: Int32 + public var optionalColor: Int32? + + public init(color: Int32, optionalColor: Int32?) { + self.color = color + self.optionalColor = optionalColor + } + + public init(decoder: PostboxDecoder) { + self.color = decoder.decodeInt32ForKey("t", orElse: 0) + self.optionalColor = decoder.decodeOptionalInt32ForKey("b") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.color, forKey: "t") + if let bottomColor = self.optionalColor { + encoder.encodeInt32(bottomColor, forKey: "b") + } else { + encoder.encodeNil(forKey: "b") + } + } + + public var colors: [UIColor] { + if let bottomColor = self.optionalColor { + return [UIColor(rgb: UInt32(bitPattern: self.color)), UIColor(rgb: UInt32(bitPattern: bottomColor))] + } else { + return [UIColor(rgb: UInt32(bitPattern: self.color))] + } + } +} + public struct PresentationThemeAccentColor: PostboxCoding, Equatable { public var baseColor: PresentationThemeBaseColor public var value: Int32? @@ -409,9 +441,9 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { } public struct PresentationThemeSettings: PreferencesEntry { - public var chatWallpaper: TelegramWallpaper public var theme: PresentationThemeReference public var themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] + public var themeSpecificBubbleColors: [Int64: PresentationThemeColorPair] public var themeSpecificChatWallpapers: [Int64: TelegramWallpaper] public var fontSize: PresentationFontSize public var automaticThemeSwitchSetting: AutomaticThemeSwitchSetting @@ -419,7 +451,7 @@ public struct PresentationThemeSettings: PreferencesEntry { public var disableAnimations: Bool private func wallpaperResources(_ wallpaper: TelegramWallpaper) -> [MediaResourceId] { - switch self.chatWallpaper { + switch wallpaper { case let .image(representations, _): return representations.map { $0.resource.id } case let .file(_, _, _, _, _, _, _, file, _): @@ -434,7 +466,6 @@ public struct PresentationThemeSettings: PreferencesEntry { public var relatedResources: [MediaResourceId] { var resources: [MediaResourceId] = [] - resources.append(contentsOf: wallpaperResources(self.chatWallpaper)) for (_, chatWallpaper) in self.themeSpecificChatWallpapers { resources.append(contentsOf: wallpaperResources(chatWallpaper)) } @@ -455,13 +486,13 @@ public struct PresentationThemeSettings: PreferencesEntry { } public static var defaultSettings: PresentationThemeSettings { - return PresentationThemeSettings(chatWallpaper: .builtin(WallpaperSettings()), theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.night)), largeEmoji: true, disableAnimations: true) + return PresentationThemeSettings(theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificBubbleColors: [:], themeSpecificChatWallpapers: [:], fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.night)), largeEmoji: true, disableAnimations: true) } - public init(chatWallpaper: TelegramWallpaper, theme: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, largeEmoji: Bool, disableAnimations: Bool) { - self.chatWallpaper = chatWallpaper + public init(theme: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificBubbleColors: [Int64: PresentationThemeColorPair], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, largeEmoji: Bool, disableAnimations: Bool) { self.theme = theme self.themeSpecificAccentColors = themeSpecificAccentColors + self.themeSpecificBubbleColors = themeSpecificBubbleColors self.themeSpecificChatWallpapers = themeSpecificChatWallpapers self.fontSize = fontSize self.automaticThemeSwitchSetting = automaticThemeSwitchSetting @@ -470,7 +501,6 @@ public struct PresentationThemeSettings: PreferencesEntry { } public init(decoder: PostboxDecoder) { - self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin(WallpaperSettings()) self.theme = decoder.decodeObjectForKey("t", decoder: { PresentationThemeReference(decoder: $0) }) as? PresentationThemeReference ?? .builtin(.dayClassic) self.themeSpecificChatWallpapers = decoder.decodeObjectDictionaryForKey("themeSpecificChatWallpapers", keyDecoder: { decoder in @@ -485,6 +515,12 @@ public struct PresentationThemeSettings: PreferencesEntry { return PresentationThemeAccentColor(decoder: decoder) }) + self.themeSpecificBubbleColors = decoder.decodeObjectDictionaryForKey("themeSpecificBubbleColors", keyDecoder: { decoder in + return decoder.decodeInt64ForKey("k", orElse: 0) + }, valueDecoder: { decoder in + return PresentationThemeColorPair(decoder: decoder) + }) + if self.themeSpecificAccentColors[PresentationThemeReference.builtin(.day).index] == nil, let themeAccentColor = decoder.decodeOptionalInt32ForKey("themeAccentColor") { let baseColor: PresentationThemeBaseColor switch themeAccentColor { @@ -517,11 +553,13 @@ public struct PresentationThemeSettings: PreferencesEntry { } public func encode(_ encoder: PostboxEncoder) { - encoder.encodeObject(self.chatWallpaper, forKey: "w") encoder.encodeObject(self.theme, forKey: "t") encoder.encodeObjectDictionary(self.themeSpecificAccentColors, forKey: "themeSpecificAccentColors", keyEncoder: { key, encoder in encoder.encodeInt64(key, forKey: "k") }) + encoder.encodeObjectDictionary(self.themeSpecificBubbleColors, forKey: "themeSpecificBubbleColors", keyEncoder: { key, encoder in + encoder.encodeInt64(key, forKey: "k") + }) encoder.encodeObjectDictionary(self.themeSpecificChatWallpapers, forKey: "themeSpecificChatWallpapers", keyEncoder: { key, encoder in encoder.encodeInt64(key, forKey: "k") }) @@ -540,7 +578,7 @@ public struct PresentationThemeSettings: PreferencesEntry { } public static func ==(lhs: PresentationThemeSettings, rhs: PresentationThemeSettings) -> Bool { - return lhs.chatWallpaper == rhs.chatWallpaper && lhs.theme == rhs.theme && lhs.themeSpecificAccentColors == rhs.themeSpecificAccentColors && lhs.themeSpecificChatWallpapers == rhs.themeSpecificChatWallpapers && lhs.fontSize == rhs.fontSize && lhs.automaticThemeSwitchSetting == rhs.automaticThemeSwitchSetting && lhs.largeEmoji == rhs.largeEmoji && lhs.disableAnimations == rhs.disableAnimations + return lhs.theme == rhs.theme && lhs.themeSpecificAccentColors == rhs.themeSpecificAccentColors && lhs.themeSpecificBubbleColors == rhs.themeSpecificBubbleColors && lhs.themeSpecificChatWallpapers == rhs.themeSpecificChatWallpapers && lhs.fontSize == rhs.fontSize && lhs.automaticThemeSwitchSetting == rhs.automaticThemeSwitchSetting && lhs.largeEmoji == rhs.largeEmoji && lhs.disableAnimations == rhs.disableAnimations } } diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index 8b90a33ef5..bbf8378e3d 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -475,7 +475,7 @@ public func patternColor(for color: UIColor, intensity: CGFloat, prominent: Bool return .black } -public func solidColor(_ color: UIColor) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func solidColorImage(_ color: UIColor) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return .single({ arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -490,6 +490,40 @@ public func solidColor(_ color: UIColor) -> Signal<(TransformImageArguments) -> }) } +public func gradientImage(_ colors: [UIColor]) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + guard !colors.isEmpty else { + return .complete() + } + guard colors.count > 1 else { + if let color = colors.first { + return solidColorImage(color) + } else { + return .complete() + } + } + return .single({ arguments in + let context = DrawingContext(size: arguments.drawingSize, clear: true) + + context.withFlippedContext { c in + let gradientColors = colors as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: CGGradientDrawingOptions()) + } + + addCorners(context, arguments: arguments) + + return context + }) +} + private func builtinWallpaperData() -> Signal { return Signal { subscriber in if let filePath = getAppBundle().path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg"), let image = UIImage(contentsOfFile: filePath) { @@ -961,6 +995,8 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the backgroundColor = UIColor(rgb: 0xd6e2ee) case let .color(color): backgroundColor = UIColor(rgb: UInt32(bitPattern: color)) + case let .gradient(topColor, bottomColor): + backgroundColor = UIColor(rgb: UInt32(bitPattern: topColor)) case .image: backgroundColor = .black case let .file(file):