Add support for gradient wallpapers

Implement new colors selection UI
This commit is contained in:
Ilya Laktyushin 2019-11-16 08:31:47 +04:00
parent 692b37fa4d
commit 78a09a38b3
33 changed files with 990 additions and 311 deletions

View File

@ -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
}

View File

@ -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()
}

View File

@ -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 {

View File

@ -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, _):

View File

@ -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<Bool>()
override public var ready: Promise<Bool> {
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) {

View File

@ -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<UIColor>(ignoreRepeated: true)
private var colorsDisposable: Disposable?
private let colors = Promise<(UIColor, (UIColor, UIColor?)?, (UIColor, UIColor?)?)>()
private let themePromise = Promise<PresentationTheme>()
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<UIColor, NoError> 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))
}
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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
}

View File

@ -289,17 +289,28 @@ public final class ThemePreviewController: ViewController {
return .single(theme)
}
}
|> mapToSignal { theme -> Signal<Void, NoError> in
if case let .cloud(info) = theme {
|> mapToSignal { updatedTheme -> Signal<Void, NoError> 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)
})
}
}

View File

@ -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()

View File

@ -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
})
}
}

View File

@ -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)
}

View File

@ -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)
})

View File

@ -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

View File

@ -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, _):

View File

@ -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 {

View File

@ -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<MediaResourceData, NoError>) -> 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<UIImage?, NoError>) -> Signal<UIColor, NoError> {
return image
|> mapToSignal { image -> Signal<UIColor, NoError> 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<UIColor, NoError> { 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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_input_add.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_input_close.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_input_change.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -580,7 +580,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
}
case let .color(color):
return solidColor(color)
return solidColorImage(color)
}
}

View File

@ -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()
}

View File

@ -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):

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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<UIImage, NoError> {
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):