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 0000000000..8c29c43c79 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorAddIcon.imageset/ic_input_add.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorRemoveIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorRemoveIcon.imageset/Contents.json new file mode 100644 index 0000000000..70373c3a41 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorRemoveIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_input_close.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorRemoveIcon.imageset/ic_input_close.pdf b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorRemoveIcon.imageset/ic_input_close.pdf new file mode 100644 index 0000000000..2c1b2eabc6 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorRemoveIcon.imageset/ic_input_close.pdf differ 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 0000000000..6151d0db8b Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Settings/ThemeColorSwapIcon.imageset/ic_input_change.pdf differ 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 ff0220da25..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/color@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/color@3x.png b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/color@3x.png deleted file mode 100644 index 7cd5551b17..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperColorIcon.imageset/color@3x.png and /dev/null differ 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):