Theming improvements

This commit is contained in:
Ilya Laktyushin 2019-12-18 09:43:40 +04:00
parent fccae60f77
commit fa3303be90
29 changed files with 3807 additions and 3093 deletions

View File

@ -5186,7 +5186,8 @@ Any member of this group will be able to see messages in the channel.";
"AuthSessions.AddDevice.UrlLoginHint" = "This code can be used to allow someone to log in to your Telegram account.\n\nTo confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code."; "AuthSessions.AddDevice.UrlLoginHint" = "This code can be used to allow someone to log in to your Telegram account.\n\nTo confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code.";
"Appearance.RemoveThemeColor" = "Remove Color"; "Appearance.RemoveThemeColor" = "Remove";
"Appearance.RemoveThemeColorConfirmation" = "Remove Color";
"WallpaperPreview.PatternTitle" = "Choose Pattern"; "WallpaperPreview.PatternTitle" = "Choose Pattern";
"WallpaperPreview.PatternPaternDiscard" = "Discard"; "WallpaperPreview.PatternPaternDiscard" = "Discard";

View File

@ -808,6 +808,39 @@ public extension ContainedViewLayoutTransition {
}) })
} }
} }
func updateTransformRotation(node: ASDisplayNode, angle: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = node.layer.transform
let currentAngle = atan2(t.m12, t.m11)
if currentAngle.isEqual(to: angle) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
node.layer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousAngle: CGFloat
if beginWithCurrentState, let presentation = node.layer.presentation() {
let t = presentation.transform
previousAngle = atan2(t.m12, t.m11)
} else {
previousAngle = currentAngle
}
node.layer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
node.layer.animateRotation(from: previousAngle, to: angle, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
} }
#if os(iOS) #if os(iOS)

View File

@ -156,12 +156,15 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE
let _ = (updatePresentationThemeSettingsInteractively(accountManager: accountManager, { current in let _ = (updatePresentationThemeSettingsInteractively(accountManager: accountManager, { current in
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
let themeReference: PresentationThemeReference
if autoNightModeTriggered { if autoNightModeTriggered {
themeSpecificChatWallpapers[current.automaticThemeSwitchSetting.theme.index] = wallpaper themeReference = current.automaticThemeSwitchSetting.theme
} else { } else {
themeSpecificChatWallpapers[current.theme.index] = wallpaper themeReference = current.theme
} }
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) let accentColorIndex = current.themeSpecificAccentColors[themeReference.index]?.index ?? 0
themeSpecificChatWallpapers[themeReference.index &+ Int64(accentColorIndex)] = wallpaper
return current.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers)
})).start() })).start()
} }

View File

@ -497,25 +497,18 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|> deliverOnMainQueue).start(next: { next in |> deliverOnMainQueue).start(next: { next in
if case let .result(resultTheme) = next { if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in let _ = (updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in if let resource = resultTheme.file?.resource, let data = themeData {
let current: PresentationThemeSettings context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
if let entry = entry as? PresentationThemeSettings { }
current = entry
} else { let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
current = PresentationThemeSettings.defaultSettings
} var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
if let resource = resultTheme.file?.resource, let data = themeData { themeSpecificChatWallpapers[themeReference.index] = nil
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
} return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper)) }) |> deliverOnMainQueue).start(completed: {
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[themeReference.index] = nil
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
})
} |> deliverOnMainQueue).start(completed: {
if !hasCustomFile { if !hasCustomFile {
saveThemeTemplateFile(state.title, themeResource, { saveThemeTemplateFile(state.title, themeResource, {
dismissImpl?() dismissImpl?()
@ -538,25 +531,18 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|> deliverOnMainQueue).start(next: { next in |> deliverOnMainQueue).start(next: { next in
if case let .result(resultTheme) = next { if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in let _ = (updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in if let resource = resultTheme.file?.resource, let data = themeData {
let current: PresentationThemeSettings context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
if let entry = entry as? PresentationThemeSettings { }
current = entry
} else { let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
current = PresentationThemeSettings.defaultSettings
} var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
if let resource = resultTheme.file?.resource, let data = themeData { themeSpecificChatWallpapers[themeReference.index] = nil
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
} return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper)) }) |> deliverOnMainQueue).start(completed: {
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[themeReference.index] = nil
return PresentationThemeSettings(theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
})
} |> deliverOnMainQueue).start(completed: {
if let themeResource = themeResource, !hasCustomFile { if let themeResource = themeResource, !hasCustomFile {
saveThemeTemplateFile(state.title, themeResource, { saveThemeTemplateFile(state.title, themeResource, {
dismissImpl?() dismissImpl?()

View File

@ -15,13 +15,13 @@ import MediaResources
private let randomBackgroundColors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e] private let randomBackgroundColors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e]
enum ThemeAccentColorControllerMode { enum ThemeAccentColorControllerMode {
case colors(themeReference: PresentationThemeReference) case colors(themeReference: PresentationThemeReference, create: Bool)
case background(themeReference: PresentationThemeReference) case background(themeReference: PresentationThemeReference)
case edit(theme: PresentationTheme, wallpaper: TelegramWallpaper?, defaultThemeReference: PresentationThemeReference?, create: Bool, completion: (PresentationTheme) -> Void) case edit(theme: PresentationTheme, wallpaper: TelegramWallpaper?, defaultThemeReference: PresentationThemeReference?, create: Bool, completion: (PresentationTheme) -> Void)
var themeReference: PresentationThemeReference? { var themeReference: PresentationThemeReference? {
switch self { switch self {
case let .colors(themeReference), let .background(themeReference): case let .colors(themeReference, _), let .background(themeReference):
return themeReference return themeReference
case let .edit(_, _, defaultThemeReference, _, _): case let .edit(_, _, defaultThemeReference, _, _):
return defaultThemeReference return defaultThemeReference
@ -37,6 +37,7 @@ final class ThemeAccentColorController: ViewController {
private let section: ThemeColorSection private let section: ThemeColorSection
private let initialBackgroundColor: UIColor? private let initialBackgroundColor: UIColor?
private var presentationData: PresentationData private var presentationData: PresentationData
private var initialAccentColor: PresentationThemeAccentColor?
private var controllerNode: ThemeAccentColorControllerNode { private var controllerNode: ThemeAccentColorControllerNode {
return self.displayNode as! ThemeAccentColorControllerNode return self.displayNode as! ThemeAccentColorControllerNode
@ -133,6 +134,7 @@ final class ThemeAccentColorController: ViewController {
}, apply: { [weak self] state, serviceBackgroundColor in }, apply: { [weak self] state, serviceBackgroundColor in
if let strongSelf = self { if let strongSelf = self {
let context = strongSelf.context let context = strongSelf.context
let initialAccentColor = strongSelf.initialAccentColor
var coloredWallpaper: TelegramWallpaper? var coloredWallpaper: TelegramWallpaper?
if let backgroundColors = state.backgroundColors { if let backgroundColors = state.backgroundColors {
@ -198,6 +200,8 @@ final class ThemeAccentColorController: ViewController {
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var themeSpecificAccentColors = current.themeSpecificAccentColors var themeSpecificAccentColors = current.themeSpecificAccentColors
var themeSpecificCustomColors = current.themeSpecificCustomColors
var customColors = themeSpecificCustomColors[currentTheme.index]?.colors ?? []
var bubbleColors: (Int32, Int32?)? var bubbleColors: (Int32, Int32?)?
if let messagesColors = state.messagesColors { if let messagesColors = state.messagesColors {
@ -208,16 +212,37 @@ final class ThemeAccentColorController: ViewController {
} }
} }
let color = PresentationThemeAccentColor(baseColor: .custom, accentColor: Int32(bitPattern: state.accentColor.rgb), bubbleColors: bubbleColors) let index: Int32
var exists: Bool
if let initialIndex = initialAccentColor?.index, initialIndex != -1 {
index = initialIndex
exists = true
} else {
index = Int32(bitPattern: arc4random())
exists = false
}
let color = PresentationThemeAccentColor(index: index, baseColor: .custom, accentColor: Int32(bitPattern: state.accentColor.rgb), bubbleColors: bubbleColors)
themeSpecificAccentColors[currentTheme.index] = color themeSpecificAccentColors[currentTheme.index] = color
if exists {
if let index = customColors.firstIndex(where: { $0.index == index }) {
customColors[index] = color
} else {
customColors.append(color)
}
} else {
customColors.append(color)
}
var wallpaper = themeSpecificChatWallpapers[currentTheme.index] var wallpaper = themeSpecificChatWallpapers[currentTheme.index]
if let coloredWallpaper = coloredWallpaper { if let coloredWallpaper = coloredWallpaper {
wallpaper = coloredWallpaper wallpaper = coloredWallpaper
} }
themeSpecificChatWallpapers[currentTheme.index] = wallpaper themeSpecificChatWallpapers[currentTheme.index &+ Int64(index)] = wallpaper
themeSpecificCustomColors[currentTheme.index] = PresentationThemeCustomColors(colors: customColors)
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificCustomColors: themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
})) |> deliverOnMainQueue).start(completed: { [weak self] in })) |> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.completion?() strongSelf.completion?()
@ -248,7 +273,7 @@ final class ThemeAccentColorController: ViewController {
var initialWallpaper: TelegramWallpaper? var initialWallpaper: TelegramWallpaper?
var backgroundColors: (UIColor, UIColor?)? var backgroundColors: (UIColor, UIColor?)?
var patternWallpaper: TelegramWallpaper? var patternWallpaper: TelegramWallpaper?
var patternIntensity: Int32 = 40 var patternIntensity: Int32 = 50
var motion = false var motion = false
let messageColors: (UIColor, UIColor?)? let messageColors: (UIColor, UIColor?)?
var defaultMessagesColor: UIColor? var defaultMessagesColor: UIColor?
@ -288,9 +313,16 @@ final class ThemeAccentColorController: ViewController {
} }
if let themeReference = strongSelf.mode.themeReference { if let themeReference = strongSelf.mode.themeReference {
accentColor = settings.themeSpecificAccentColors[themeReference.index]?.color ?? defaultDayAccentColor let themeSpecificAccentColor = settings.themeSpecificAccentColors[themeReference.index]
if case .colors(_, true) = strongSelf.mode {
} else if themeSpecificAccentColor?.baseColor == .custom {
strongSelf.initialAccentColor = themeSpecificAccentColor
}
accentColor = themeSpecificAccentColor?.color ?? defaultDayAccentColor
var wallpaper: TelegramWallpaper var wallpaper: TelegramWallpaper
if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] { if let index = themeSpecificAccentColor?.index, let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index &+ Int64(index)] {
wallpaper = customWallpaper
} else if let customWallpaper = settings.themeSpecificChatWallpapers[themeReference.index] {
wallpaper = customWallpaper wallpaper = customWallpaper
} else { } else {
let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: nil) ?? defaultPresentationTheme let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: nil) ?? defaultPresentationTheme

View File

@ -64,7 +64,7 @@ struct ThemeColorState {
self.preview = false self.preview = false
self.previousPatternWallpaper = nil self.previousPatternWallpaper = nil
self.patternWallpaper = nil self.patternWallpaper = nil
self.patternIntensity = 40 self.patternIntensity = 50
self.motion = false self.motion = false
self.defaultMessagesColor = nil self.defaultMessagesColor = nil
self.messagesColors = nil self.messagesColors = nil
@ -669,7 +669,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
} }
self.colorPanelNode.updateState({ _ in self.colorPanelNode.updateState({ _ in
return WallpaperColorPanelNodeState(selection: colorPanelCollapsed ? .none : .first, firstColor: firstColor, defaultColor: defaultColor, secondColor: secondColor, secondColorAvailable: self.state.section != .accent, rotateAvailable: self.state.section == .background, preview: false) return WallpaperColorPanelNodeState(selection: colorPanelCollapsed ? .none : .first, firstColor: firstColor, defaultColor: defaultColor, secondColor: secondColor, secondColorAvailable: self.state.section != .accent, rotateAvailable: self.state.section == .background, rotation: self.state.rotation ?? 0, preview: false)
}, animated: animated) }, animated: animated)
needsLayout = true needsLayout = true
@ -986,7 +986,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
let centerButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: layout.size.height - bottomInset - 44.0), size: buttonSize) let centerButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: layout.size.height - bottomInset - 44.0), size: buttonSize)
let rightButtonFrame = CGRect(origin: CGPoint(x: ceil(layout.size.width / 2.0 + 10.0), y: layout.size.height - bottomInset - 44.0), size: buttonSize) let rightButtonFrame = CGRect(origin: CGPoint(x: ceil(layout.size.width / 2.0 + 10.0), y: layout.size.height - bottomInset - 44.0), size: buttonSize)
var hasMotion: Bool = self.state.backgroundColors?.1 != nil || self.state.patternWallpaper != nil || self.state.displayPatternPanel var hasMotion: Bool = self.state.patternWallpaper != nil || self.state.displayPatternPanel
var patternAlpha: CGFloat = displayOptionButtons ? 1.0 : 0.0 var patternAlpha: CGFloat = displayOptionButtons ? 1.0 : 0.0
var motionAlpha: CGFloat = displayOptionButtons && hasMotion ? 1.0 : 0.0 var motionAlpha: CGFloat = displayOptionButtons && hasMotion ? 1.0 : 0.0

View File

@ -68,7 +68,7 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
case settingInfo(PresentationTheme, String) case settingInfo(PresentationTheme, String)
case themeHeader(PresentationTheme, String) case themeHeader(PresentationTheme, String)
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor]) case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper])
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
@ -186,8 +186,8 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColors): case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsThemeChatWallpapers):
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsThemeAccentColors) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors { if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsThemeAccentColors, rhsThemeChatWallpapers) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors, lhsThemeChatWallpapers == rhsThemeChatWallpapers {
return true return true
} else { } else {
return false return false
@ -244,8 +244,8 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .themeHeader(theme, title): case let .themeHeader(theme, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors): case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers):
return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, displayUnsupported: false, themeSpecificAccentColors: themeSpecificAccentColors, currentTheme: currentTheme, updatedTheme: { theme in return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, displayUnsupported: false, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, currentTheme: currentTheme, updatedTheme: { theme in
arguments.updateTheme(theme) arguments.updateTheme(theme)
}, contextAction: nil) }, contextAction: nil)
} }
@ -313,7 +313,7 @@ private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, s
break break
case .system, .timeBased, .brightness: case .system, .timeBased, .brightness:
entries.append(.themeHeader(theme, strings.AutoNightTheme_PreferredTheme)) entries.append(.themeHeader(theme, strings.AutoNightTheme_PreferredTheme))
entries.append(.themeItem(theme, strings, availableThemes, switchSetting.theme, settings.themeSpecificAccentColors)) entries.append(.themeItem(theme, strings, availableThemes, switchSetting.theme, settings.themeSpecificAccentColors, settings.themeSpecificChatWallpapers))
} }
return entries return entries

View File

@ -182,8 +182,16 @@ final class ThemeGridController: ViewController {
let presentationData = strongSelf.presentationData let presentationData = strongSelf.presentationData
let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
let themeReference: PresentationThemeReference
if presentationData.autoNightModeTriggered {
themeReference = current.automaticThemeSwitchSetting.theme
} else {
themeReference = current.theme
}
let accentColorIndex = current.themeSpecificAccentColors[themeReference.index]?.index ?? 0
themeSpecificChatWallpapers[current.theme.index] = nil themeSpecificChatWallpapers[current.theme.index] = nil
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) themeSpecificChatWallpapers[current.theme.index &+ Int64(accentColorIndex)] = nil
return current.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers)
})).start() })).start()
break break
} }
@ -233,18 +241,8 @@ final class ThemeGridController: ViewController {
let _ = resetWallpapers(account: strongSelf.context.account).start(completed: { [weak self, weak controller] in let _ = resetWallpapers(account: strongSelf.context.account).start(completed: { [weak self, weak controller] in
let presentationData = strongSelf.presentationData let presentationData = strongSelf.presentationData
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> Void in let _ = updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in return current.withUpdatedThemeSpecificChatWallpapers([:])
let current: PresentationThemeSettings
if let entry = entry as? PresentationThemeSettings {
current = entry
} else {
current = PresentationThemeSettings.defaultSettings
}
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[current.theme.index] = nil
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: [:], useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
})
}).start() }).start()
let _ = (telegramWallpapers(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network) let _ = (telegramWallpapers(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network)

View File

@ -322,25 +322,21 @@ public final class ThemePreviewController: ViewController {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: info.theme).start() 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() 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
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current -> PresentationThemeSettings in
var updated: PresentationThemeSettings
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in if autoNightModeTriggered {
let current = entry as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[updatedTheme.index] = nil
var theme = current.theme
var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting
if autoNightModeTriggered { automaticThemeSwitchSetting.theme = updatedTheme
automaticThemeSwitchSetting.theme = updatedTheme updated = current.withUpdatedAutomaticThemeSwitchSetting(automaticThemeSwitchSetting)
} else { } else {
theme = updatedTheme updated = current.withUpdatedTheme(updatedTheme)
} }
return PresentationThemeSettings(theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
}) themeSpecificChatWallpapers[updatedTheme.index] = nil
} return updated.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers)
})
} }
var cancelImpl: (() -> Void)? var cancelImpl: (() -> Void)?

View File

@ -7,74 +7,392 @@ import TelegramCore
import SyncCore import SyncCore
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences import TelegramUIPreferences
import MergeLists
import ItemListUI import ItemListUI
import ContextUI import ContextUI
import PresentationDataUtils import PresentationDataUtils
private func generateSwatchImage(theme: PresentationTheme, themeReference: PresentationThemeReference, color: PresentationThemeAccentColor, bubbles: (UIColor, UIColor?)?, selected: Bool, more: Bool) -> UIImage? { private enum ThemeSettingsColorEntryId: Hashable {
case color(Int)
case picker
}
private enum ThemeSettingsColorEntry: Comparable, Identifiable {
case color(Int, PresentationThemeReference, PresentationThemeAccentColor?, Bool)
case picker
var stableId: ThemeSettingsColorEntryId {
switch self {
case let .color(index, _, _, _):
return .color(index)
case .picker:
return .picker
}
}
static func ==(lhs: ThemeSettingsColorEntry, rhs: ThemeSettingsColorEntry) -> Bool {
switch lhs {
case let .color(lhsIndex, lhsThemeReference, lhsAccentColor, lhsSelected):
if case let .color(rhsIndex, rhsThemeReference, rhsAccentColor, rhsSelected) = rhs, lhsIndex == rhsIndex, lhsThemeReference.index == rhsThemeReference.index, lhsAccentColor == rhsAccentColor, lhsSelected == rhsSelected {
return true
} else {
return false
}
case .picker:
if case .picker = rhs {
return true
} else {
return false
}
}
}
static func <(lhs: ThemeSettingsColorEntry, rhs: ThemeSettingsColorEntry) -> Bool {
switch lhs {
case let .color(lhsIndex, _, _, _):
switch rhs {
case let .color(rhsIndex, _, _, _):
return lhsIndex < rhsIndex
case .picker:
return true
}
case .picker:
return false
}
}
func item(action: @escaping (PresentationThemeAccentColor?, Bool) -> Void, contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void) -> ListViewItem {
switch self {
case let .color(_, themeReference, accentColor, selected):
return ThemeSettingsAccentColorIconItem(themeReference: themeReference, accentColor: accentColor, selected: selected, action: action, contextAction: contextAction)
case .picker:
return ThemeSettingsAccentColorPickerItem(action: openColorPicker)
}
}
}
private class ThemeSettingsAccentColorIconItem: ListViewItem {
let themeReference: PresentationThemeReference
let accentColor: PresentationThemeAccentColor?
let selected: Bool
let action: (PresentationThemeAccentColor?, Bool) -> Void
let contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?
public init(themeReference: PresentationThemeReference, accentColor: PresentationThemeAccentColor?, selected: Bool, action: @escaping (PresentationThemeAccentColor?, Bool) -> Void, contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?) {
self.themeReference = themeReference
self.accentColor = accentColor
self.selected = selected
self.action = action
self.contextAction = contextAction
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ThemeSettingsAccentColorIconItemNode()
let (nodeLayout, apply) = node.asyncLayout()(self, params)
node.insets = nodeLayout.insets
node.contentSize = nodeLayout.contentSize
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in
apply(false)
})
})
}
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
assert(node() is ThemeSettingsAccentColorIconItemNode)
if let nodeValue = node() as? ThemeSettingsAccentColorIconItemNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params)
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply(animation.isAnimated)
})
}
}
}
}
}
public var selectable = true
public func selected(listView: ListView) {
self.action(self.accentColor, self.selected)
}
}
private func generateRingImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds) context.clear(bounds)
let fillColor = color.color context.setStrokeColor(color.cgColor)
var strokeColor = color.baseColor.color
if strokeColor == .clear {
strokeColor = fillColor
}
if strokeColor.distance(to: theme.list.itemBlocksBackgroundColor) < 200 {
if strokeColor.distance(to: UIColor.white) < 200 {
strokeColor = UIColor(rgb: 0x999999)
} else {
strokeColor = theme.list.controlSecondaryColor
}
}
context.setFillColor(fillColor.cgColor)
context.setStrokeColor(strokeColor.cgColor)
context.setLineWidth(2.0) context.setLineWidth(2.0)
context.strokeEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0))
if selected { })
context.fillEllipse(in: bounds.insetBy(dx: 4.0, dy: 4.0))
context.strokeEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0))
if more {
context.setFillColor(UIColor.white.cgColor)
let dotSize = CGSize(width: 4.0, height: 4.0)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 11.0, y: 18.0), size: dotSize))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 18.0, y: 18.0), size: dotSize))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 25.0, y: 18.0), size: dotSize))
}
} else {
context.fillEllipse(in: bounds)
context.saveGState()
context.addEllipse(in: bounds.insetBy(dx: 10.0, dy: 10.0))
context.clip()
var colors: (UIColor, UIColor)?
if let customColors = bubbles {
colors = (customColors.0, customColors.1 ?? customColors.0)
} else if case .builtin(.dayClassic) = themeReference {
let hsb = color.color.hsb
let bubbleColor = UIColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0)
colors = (bubbleColor, bubbleColor)
}
if let colors = colors {
let gradientColors = [colors.0.cgColor, colors.1.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: 10.0), end: CGPoint(x: 0.0, y: size.height - 10.0), options: CGGradientDrawingOptions())
}
context.restoreGState()
}
})?.stretchableImage(withLeftCapWidth: 15, topCapHeight: 15)
} }
private func generateFillImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(color.cgColor)
context.fillEllipse(in: bounds.insetBy(dx: 4.0, dy: 4.0))
})
}
private func generateCenterImage(topColor: UIColor, bottomColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.addEllipse(in: bounds.insetBy(dx: 10.0, dy: 10.0))
context.clip()
let gradientColors = [topColor.cgColor, 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: 10.0), end: CGPoint(x: 0.0, y: size.height - 10.0), options: CGGradientDrawingOptions())
})
}
private func generateDotsImage() -> UIImage? {
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(UIColor.white.cgColor)
let dotSize = CGSize(width: 4.0, height: 4.0)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 11.0, y: 18.0), size: dotSize))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 18.0, y: 18.0), size: dotSize))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 25.0, y: 18.0), size: dotSize))
})
}
private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
private let containerNode: ContextControllerSourceNode
private let fillNode: ASImageNode
private let ringNode: ASImageNode
private let centerNode: ASImageNode
private let dotsNode: ASImageNode
var item: ThemeSettingsAccentColorIconItem?
init() {
self.containerNode = ContextControllerSourceNode()
self.fillNode = ASImageNode()
self.fillNode.displaysAsynchronously = false
self.fillNode.displayWithoutProcessing = true
self.ringNode = ASImageNode()
self.ringNode.displaysAsynchronously = false
self.ringNode.displayWithoutProcessing = true
self.centerNode = ASImageNode()
self.centerNode.displaysAsynchronously = false
self.centerNode.displayWithoutProcessing = true
self.dotsNode = ASImageNode()
self.dotsNode.displaysAsynchronously = false
self.dotsNode.displayWithoutProcessing = true
self.dotsNode.image = generateDotsImage()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.fillNode)
self.containerNode.addSubnode(self.ringNode)
self.containerNode.addSubnode(self.dotsNode)
self.containerNode.addSubnode(self.centerNode)
self.containerNode.activated = { [weak self] gesture in
guard let strongSelf = self, let item = strongSelf.item else {
gesture.cancel()
return
}
item.contextAction?(item.accentColor, strongSelf.containerNode, gesture)
}
}
override func didLoad() {
super.didLoad()
self.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
}
func setSelected(_ selected: Bool, animated: Bool = false) {
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate
if selected {
transition.updateTransformScale(node: self.fillNode, scale: 1.0)
transition.updateTransformScale(node: self.centerNode, scale: 0.16)
transition.updateAlpha(node: self.centerNode, alpha: 0.0)
transition.updateTransformScale(node: self.dotsNode, scale: 1.0)
transition.updateAlpha(node: self.dotsNode, alpha: 1.0)
} else {
transition.updateTransformScale(node: self.fillNode, scale: 1.2)
transition.updateTransformScale(node: self.centerNode, scale: 1.0)
transition.updateAlpha(node: self.centerNode, alpha: 1.0)
transition.updateTransformScale(node: self.dotsNode, scale: 0.7)
transition.updateAlpha(node: self.dotsNode, alpha: 0.0)
}
}
func asyncLayout() -> (ThemeSettingsAccentColorIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let currentItem = self.item
return { [weak self] item, params in
var updatedAccentColor = false
var updatedSelected = false
if currentItem == nil || currentItem?.accentColor != item.accentColor {
updatedAccentColor = true
}
if currentItem?.selected != item.selected {
updatedSelected = true
}
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 60.0, height: 58.0), insets: UIEdgeInsets())
return (itemLayout, { animated in
if let strongSelf = self {
strongSelf.item = item
if updatedAccentColor {
var fillColor = item.accentColor?.color
var strokeColor = item.accentColor?.baseColor.color
if strokeColor == .clear {
strokeColor = fillColor
}
// if strokeColor.distance(to: theme.list.itemBlocksBackgroundColor) < 200 {
// if strokeColor.distance(to: UIColor.white) < 200 {
// strokeColor = UIColor(rgb: 0x999999)
// } else {
// strokeColor = theme.list.controlSecondaryColor
// }
// }
var topColor: UIColor?
var bottomColor: UIColor?
if let colors = item.accentColor?.plainBubbleColors {
topColor = colors.0
bottomColor = colors.1
} else if case .builtin(.dayClassic) = item.themeReference {
if let accentColor = item.accentColor {
let hsb = accentColor.color.hsb
let bubbleColor = UIColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0)
topColor = bubbleColor
bottomColor = bubbleColor
} else {
fillColor = UIColor(rgb: 0x007ee5)
strokeColor = fillColor
topColor = UIColor(rgb: 0xe1ffc7)
bottomColor = topColor
}
}
strongSelf.fillNode.image = generateFillImage(color: fillColor ?? .clear)
strongSelf.ringNode.image = generateRingImage(color: strokeColor ?? .clear)
strongSelf.centerNode.image = generateCenterImage(topColor: topColor ?? .clear, bottomColor: bottomColor ?? .clear)
}
let center = CGPoint(x: 30.0, y: 29.0)
let bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 40.0, height: 40.0))
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.contentSize)
strongSelf.fillNode.position = center
strongSelf.ringNode.position = center
strongSelf.centerNode.position = center
strongSelf.dotsNode.position = center
strongSelf.fillNode.bounds = bounds
strongSelf.ringNode.bounds = bounds
strongSelf.centerNode.bounds = bounds
strongSelf.dotsNode.bounds = bounds
if updatedSelected {
strongSelf.setSelected(item.selected, animated: currentItem != nil)
}
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
super.animateRemoved(currentTimestamp, duration: duration)
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
super.animateAdded(currentTimestamp, duration: duration)
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
private class ThemeSettingsAccentColorPickerItem: ListViewItem {
let action: (Bool) -> Void
public init(action: @escaping (Bool) -> Void) {
self.action = action
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ThemeSettingsAccentColorPickerItemNode()
let (nodeLayout, apply) = node.asyncLayout()(self, params)
node.insets = nodeLayout.insets
node.contentSize = nodeLayout.contentSize
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in
apply(false)
})
})
}
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
assert(node() is ThemeSettingsAccentColorPickerItemNode)
if let nodeValue = node() as? ThemeSettingsAccentColorPickerItemNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params)
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply(animation.isAnimated)
})
}
}
}
}
}
public var selectable = true
public func selected(listView: ListView) {
self.action(true)
}
}
private func generateCustomSwatchImage() -> UIImage? { private func generateCustomSwatchImage() -> UIImage? {
return generateImage(CGSize(width: 42.0, height: 42.0), rotatedContext: { size, context in return generateImage(CGSize(width: 42.0, height: 42.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
@ -105,9 +423,78 @@ private func generateCustomSwatchImage() -> UIImage? {
}) })
} }
private final class ThemeSettingsAccentColorPickerItemNode : ListViewItemNode {
private let imageNode: ASImageNode
var item: ThemeSettingsAccentColorPickerItem?
init() {
self.imageNode = ASImageNode()
self.imageNode.displaysAsynchronously = false
self.imageNode.displayWithoutProcessing = true
self.imageNode.image = generateCustomSwatchImage()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.imageNode)
}
override func didLoad() {
super.didLoad()
self.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
}
func asyncLayout() -> (ThemeSettingsAccentColorPickerItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let currentItem = self.item
return { [weak self] item, params in
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 60.0, height: 60.0), insets: UIEdgeInsets())
return (itemLayout, { animated in
if let strongSelf = self {
strongSelf.item = item
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
super.animateRemoved(currentTimestamp, duration: duration)
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
super.animateAdded(currentTimestamp, duration: duration)
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
enum ThemeSettingsAccentColor { enum ThemeSettingsAccentColor {
case `default` case `default`
case color(PresentationThemeBaseColor) case color(PresentationThemeBaseColor)
case preset(PresentationThemeAccentColor)
case custom(PresentationThemeAccentColor)
var index: Int32? {
switch self {
case .default:
return nil
case let .color(color):
return 10 + color.rawValue
case let .preset(color), let .custom(color):
return color.index
}
}
} }
class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem { class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
@ -119,10 +506,10 @@ class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
let currentColor: PresentationThemeAccentColor? let currentColor: PresentationThemeAccentColor?
let updated: (PresentationThemeAccentColor?) -> Void let updated: (PresentationThemeAccentColor?) -> Void
let contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)? let contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?
let openColorPicker: () -> Void let openColorPicker: (Bool) -> Void
let tag: ItemListItemTag? let tag: ItemListItemTag?
init(theme: PresentationTheme, sectionId: ItemListSectionId, themeReference: PresentationThemeReference, colors: [ThemeSettingsAccentColor], currentColor: PresentationThemeAccentColor?, updated: @escaping (PresentationThemeAccentColor?) -> Void, contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping () -> Void, tag: ItemListItemTag? = nil) { init(theme: PresentationTheme, sectionId: ItemListSectionId, themeReference: PresentationThemeReference, colors: [ThemeSettingsAccentColor], currentColor: PresentationThemeAccentColor?, updated: @escaping (PresentationThemeAccentColor?) -> Void, contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, tag: ItemListItemTag? = nil) {
self.theme = theme self.theme = theme
self.themeReference = themeReference self.themeReference = themeReference
self.colors = colors self.colors = colors
@ -168,66 +555,39 @@ class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
} }
} }
private final class ThemeSettingsAccentColorNode : ASDisplayNode { private struct ThemeSettingsAccentColorItemNodeTransition {
private let containerNode: ContextControllerSourceNode let deletions: [ListViewDeleteItem]
private let iconNode: ASImageNode let insertions: [ListViewInsertItem]
private var action: (() -> Void)? let updates: [ListViewUpdateItem]
private var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? let crossfade: Bool
override init() {
self.containerNode = ContextControllerSourceNode()
self.iconNode = ASImageNode()
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
self.iconNode.isLayerBacked = true
super.init()
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.iconNode)
self.containerNode.activated = { [weak self] gesture in
guard let strongSelf = self else {
gesture.cancel()
return
}
strongSelf.contextAction?(strongSelf.containerNode, gesture)
}
}
func setup(theme: PresentationTheme, themeReference: PresentationThemeReference, isDefault: Bool, color: PresentationThemeAccentColor, bubbles: (UIColor, UIColor?)?, selected: Bool, more: Bool, action: @escaping () -> Void, contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?) {
self.iconNode.image = generateSwatchImage(theme: theme, themeReference: themeReference, color: color, bubbles: bubbles, selected: selected, more: more)
self.action = {
action()
}
self.contextAction = { node, gesture in
contextAction?(themeReference, isDefault ? nil : color, node, gesture)
}
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.action?()
}
}
override func layout() {
super.layout()
self.containerNode.frame = self.bounds
self.iconNode.frame = self.containerNode.bounds
}
} }
private func preparedTransition(action: @escaping (PresentationThemeAccentColor?, Bool) -> Void, contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, from fromEntries: [ThemeSettingsColorEntry], to toEntries: [ThemeSettingsColorEntry], crossfade: Bool) -> ThemeSettingsAccentColorItemNodeTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(action: action, contextAction: contextAction, openColorPicker: openColorPicker), directionHint: .Down) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(action: action, contextAction: contextAction, openColorPicker: openColorPicker), directionHint: nil) }
return ThemeSettingsAccentColorItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade)
}
private let textFont = Font.regular(11.0) private func ensureColorVisible(listNode: ListView, accentColor: PresentationThemeAccentColor?, animated: Bool) -> Bool {
private let itemSize = Font.regular(11.0) var resultNode: ThemeSettingsAccentColorIconItemNode?
listNode.forEachItemNode { node in
if resultNode == nil, let node = node as? ThemeSettingsAccentColorIconItemNode {
if node.item?.accentColor?.index == accentColor?.index {
resultNode = node
}
}
}
if let resultNode = resultNode {
listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 24.0)
return true
} else {
return false
}
}
class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
private let containerNode: ASDisplayNode private let containerNode: ASDisplayNode
@ -237,9 +597,10 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
private let maskNode: ASImageNode private let maskNode: ASImageNode
private var snapshotView: UIView? private var snapshotView: UIView?
private let scrollNode: ASScrollNode private let listNode: ListView
private var colorNodes: [ThemeSettingsAccentColorNode] = [] private var entries: [ThemeSettingsColorEntry]?
private let customNode: HighlightableButtonNode private var enqueuedTransitions: [ThemeSettingsAccentColorItemNodeTransition] = []
private var initialized = false
private var item: ThemeSettingsAccentColorItem? private var item: ThemeSettingsAccentColorItem?
private var layoutParams: ListViewItemLayoutParams? private var layoutParams: ListViewItemLayoutParams?
@ -262,40 +623,53 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
self.maskNode = ASImageNode() self.maskNode = ASImageNode()
self.scrollNode = ASScrollNode() self.listNode = ListView()
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
self.customNode = HighlightableButtonNode()
super.init(layerBacked: false, dynamicBounce: false) super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.addSubnode(self.listNode)
self.customNode.setImage(generateCustomSwatchImage(), for: .normal)
self.customNode.addTarget(self, action: #selector(customPressed), forControlEvents: .touchUpInside)
self.addSubnode(self.scrollNode)
self.scrollNode.addSubnode(self.customNode)
} }
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.scrollNode.view.showsHorizontalScrollIndicator = false
} }
@objc func customPressed() { private func enqueueTransition(_ transition: ThemeSettingsAccentColorItemNodeTransition) {
self.item?.openColorPicker() self.enqueuedTransitions.append(transition)
}
private func scrollToNode(_ node: ThemeSettingsAccentColorNode, animated: Bool) {
let bounds = self.scrollNode.view.bounds
let frame = node.frame.insetBy(dx: -48.0, dy: 0.0)
if frame.minX < bounds.minX || frame.maxX > bounds.maxX { if let _ = self.item {
self.scrollNode.view.scrollRectToVisible(frame, animated: animated) while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
}
} }
} }
private func dequeueTransition() {
guard let item = self.item, let transition = self.enqueuedTransitions.first else {
return
}
self.enqueuedTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions()
if self.initialized && transition.crossfade {
options.insert(.AnimateCrossfade)
}
var scrollToItem: ListViewScrollToItem?
if !self.initialized {
if let index = item.colors.firstIndex(where: { $0.index == item.currentColor?.index }) {
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-24.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
self.initialized = true
}
}
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in
})
}
func asyncLayout() -> (_ item: ThemeSettingsAccentColorItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { func asyncLayout() -> (_ item: ThemeSettingsAccentColorItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item let currentItem = self.item
@ -320,7 +694,6 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.item = item strongSelf.item = item
strongSelf.layoutParams = params strongSelf.layoutParams = params
strongSelf.scrollNode.view.contentInset = UIEdgeInsets(top: 0.0, left: params.leftInset, bottom: 0.0, right: params.rightInset)
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
@ -369,83 +742,66 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
strongSelf.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layoutSize.width, height: layoutSize.height)) var listInsets = UIEdgeInsets()
listInsets.top += params.leftInset + 4.0
listInsets.bottom += params.rightInset + 4.0
let nodeInset: CGFloat = 15.0 strongSelf.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width)
let nodeSize = CGSize(width: 40.0, height: 40.0) strongSelf.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0)
var nodeOffset = nodeInset strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
var updated = false var entries: [ThemeSettingsColorEntry] = []
var selectedNode: ThemeSettingsAccentColorNode? var index: Int = 0
var i = 0
for color in item.colors { for color in item.colors {
let imageNode: ThemeSettingsAccentColorNode
if strongSelf.colorNodes.count > i {
imageNode = strongSelf.colorNodes[i]
} else {
imageNode = ThemeSettingsAccentColorNode()
strongSelf.colorNodes.append(imageNode)
strongSelf.scrollNode.addSubnode(imageNode)
updated = true
}
let selected: Bool
var accentColor: PresentationThemeAccentColor
var itemColor: PresentationThemeAccentColor?
var isDefault = false
switch color { switch color {
case .default: case .default:
selected = item.currentColor == nil let selected = item.currentColor == nil
accentColor = PresentationThemeAccentColor(baseColor: .blue, accentColor: 0x007ee5, bubbleColors: (0xe1ffc7, nil)) entries.append(.color(index, item.themeReference, nil, selected))
isDefault = true
case let .color(color): case let .color(color):
selected = item.currentColor?.baseColor == color let selected = item.currentColor?.baseColor == color
let accentColor: PresentationThemeAccentColor
if let currentColor = item.currentColor, selected { if let currentColor = item.currentColor, selected {
accentColor = currentColor accentColor = currentColor
} else { } else {
accentColor = PresentationThemeAccentColor(baseColor: color) accentColor = PresentationThemeAccentColor(index: 10 + color.rawValue, baseColor: color)
} }
itemColor = accentColor entries.append(.color(index, item.themeReference, accentColor, selected))
case let .preset(color), let .custom(color):
let selected = item.currentColor == color
entries.append(.color(index, item.themeReference, color, selected))
} }
index += 1
}
entries.append(.picker)
if selected { let action: (PresentationThemeAccentColor?, Bool) -> Void = { [weak self] color, selected in
selectedNode = imageNode if let strongSelf = self, let item = strongSelf.item {
}
imageNode.setup(theme: item.theme, themeReference: item.themeReference, isDefault: isDefault, color: accentColor, bubbles: accentColor.customBubbleColors, selected: selected, more: true, action: { [weak self, weak imageNode] in
if selected { if selected {
item.openColorPicker() item.openColorPicker(color?.baseColor != .custom)
} else { } else {
item.updated(itemColor) item.updated(color)
} }
if let imageNode = imageNode { ensureColorVisible(listNode: strongSelf.listNode, accentColor: color, animated: true)
self?.scrollToNode(imageNode, animated: true) }
} }
}, contextAction: item.contextAction) let contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)? = { [weak item] color, node, gesture in
if let strongSelf = self, let item = strongSelf.item {
imageNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 10.0), size: nodeSize) item.contextAction?(item.themeReference, color, node, gesture)
nodeOffset += nodeSize.width + 18.0 }
}
i += 1 let openColorPicker: (Bool) -> Void = { [weak self] create in
if let strongSelf = self, let item = strongSelf.item {
item.openColorPicker(true)
}
} }
strongSelf.customNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 9.0), size: CGSize(width: 42.0, height: 42.0)) let previousEntries = strongSelf.entries ?? []
let crossfade = themeUpdated || previousEntries.count != entries.count
let transition = preparedTransition(action: action, contextAction: contextAction, openColorPicker: openColorPicker, from: previousEntries, to: entries, crossfade: crossfade)
strongSelf.enqueueTransition(transition)
for k in (i ..< strongSelf.colorNodes.count).reversed() { strongSelf.entries = entries
let node = strongSelf.colorNodes[k]
strongSelf.colorNodes.remove(at: k)
node.removeFromSupernode()
}
let contentSize = CGSize(width: strongSelf.customNode.frame.maxX + nodeInset, height: strongSelf.scrollNode.frame.height)
if strongSelf.scrollNode.view.contentSize != contentSize {
strongSelf.scrollNode.view.contentSize = contentSize
}
if updated, let selectedNode = selectedNode {
strongSelf.scrollToNode(selectedNode, animated: false)
}
} }
}) })
} }

View File

@ -11,6 +11,7 @@ import ItemListUI
import PresentationDataUtils import PresentationDataUtils
import AlertUI import AlertUI
import PresentationDataUtils import PresentationDataUtils
import MediaResources
import WallpaperResources import WallpaperResources
import ShareController import ShareController
import AccountContext import AccountContext
@ -72,7 +73,7 @@ private final class ThemeSettingsControllerArguments {
let selectFontSize: (PresentationFontSize) -> Void let selectFontSize: (PresentationFontSize) -> Void
let openWallpaperSettings: () -> Void let openWallpaperSettings: () -> Void
let selectAccentColor: (PresentationThemeAccentColor?) -> Void let selectAccentColor: (PresentationThemeAccentColor?) -> Void
let openAccentColorPicker: (PresentationThemeReference) -> Void let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
let openAutoNightTheme: () -> Void let openAutoNightTheme: () -> Void
let openTextSize: () -> Void let openTextSize: () -> Void
let toggleLargeEmoji: (Bool) -> Void let toggleLargeEmoji: (Bool) -> Void
@ -82,7 +83,7 @@ private final class ThemeSettingsControllerArguments {
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void let colorContextAction: (PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void) { init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context self.context = context
self.selectTheme = selectTheme self.selectTheme = selectTheme
self.selectFontSize = selectFontSize self.selectFontSize = selectFontSize
@ -132,10 +133,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case fontSize(PresentationTheme, PresentationFontSize) case fontSize(PresentationTheme, PresentationFontSize)
case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem]) case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case wallpaper(PresentationTheme, String) case wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeAccentColor?) case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeCustomColors?, PresentationThemeAccentColor?)
case autoNightTheme(PresentationTheme, String, String) case autoNightTheme(PresentationTheme, String, String)
case textSize(PresentationTheme, String, String) case textSize(PresentationTheme, String, String)
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], PresentationThemeAccentColor?) case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?)
case iconHeader(PresentationTheme, String) case iconHeader(PresentationTheme, String)
case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?) case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?)
case otherHeader(PresentationTheme, String) case otherHeader(PresentationTheme, String)
@ -207,8 +208,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .accentColor(lhsTheme, lhsCurrentTheme, lhsColor): case let .accentColor(lhsTheme, lhsCurrentTheme, lhsCustomColors, lhsColor):
if case let .accentColor(rhsTheme, rhsCurrentTheme, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsColor == rhsColor { if case let .accentColor(rhsTheme, rhsCurrentTheme, rhsCustomColors, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsCustomColors == rhsCustomColors, lhsColor == rhsColor {
return true return true
} else { } else {
return false return false
@ -231,8 +232,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsCurrentColor): case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsThemeSpecificChatWallpapers, lhsCurrentColor):
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsThemeAccentColors, rhsCurrentColor) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors, lhsCurrentColor == rhsCurrentColor { if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsThemeAccentColors, rhsThemeSpecificChatWallpapers, rhsCurrentColor) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors, lhsThemeSpecificChatWallpapers == rhsThemeSpecificChatWallpapers, lhsCurrentColor == rhsCurrentColor {
return true return true
} else { } else {
return false return false
@ -307,14 +308,25 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openWallpaperSettings() arguments.openWallpaperSettings()
}) })
case let .accentColor(theme, currentTheme, color): case let .accentColor(theme, currentTheme, customColors, color):
var colorItems: [ThemeSettingsAccentColor] = [] var colorItems: [ThemeSettingsAccentColor] = []
var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue) var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue)
var colors = PresentationThemeBaseColor.allCases var colors = PresentationThemeBaseColor.allCases
colors = colors.filter { $0 != .custom && $0 != .preset }
if case let .builtin(name) = currentTheme { if case let .builtin(name) = currentTheme {
if name == .dayClassic { if name == .dayClassic {
colorItems.append(.default) colorItems.append(.default)
defaultColor = nil defaultColor = nil
let createPaper: (String, Int32, Int32?, Int32?, Int32?) -> TelegramWallpaper = { slug, topColor, bottomColor, intensity, rotation in
return TelegramWallpaper.file(id: 0, accessHash: 0, isCreator: false, isDefault: true, isPattern: true, isDark: false, slug: slug, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(blur: false, motion: false, color: topColor, bottomColor: bottomColor, intensity: intensity ?? 50, rotation: rotation))
}
colorItems.append(.preset(PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x7e5fe5, bubbleColors: (0xf5e2ff, nil), wallpaper: createPaper("nQcFYJe1mFIBAAAAcI95wtIK0fk", 0xfcccf4, 0xae85f0, 54, nil)))) // amethyst dust
colorItems.append(.preset(PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0xff5fa9, bubbleColors: (0xfff4d7, nil), wallpaper: createPaper("51nnTjx8mFIBAAAAaFGJsMIvWkk", 0xf6b594, 0xebf6cd, 46, 45)))) // bubbly
colorItems.append(.preset(PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0x199972, bubbleColors: (0xfffec7, nil), wallpaper: createPaper("fqv01SQemVIBAAAApND8LDRUhRU", 0xc1e7cb, nil, 50, nil)))) // downtown
colorItems.append(.preset(PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0x5a9e29, bubbleColors: (0xdcf8c6, nil), wallpaper: createPaper("R3j69wKskFIBAAAAoUdXWCKMzCM", 0xede6dd, nil, 50, nil)))) // green
colorItems.append(.preset(PresentationThemeAccentColor(index: 105, baseColor: .preset, accentColor: 0x009eee, bubbleColors: (0x94fff9, 0xccffc7), wallpaper: createPaper("p-pXcflrmFIBAAAAvXYQk-mCwZU", 0xffbca6, 0xff63bd, 57, 225)))) // blue lolly
} }
if name != .day { if name != .day {
colors = colors.filter { $0 != .black } colors = colors.filter { $0 != .black }
@ -327,18 +339,22 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} }
} }
let currentColor = color ?? defaultColor let currentColor = color ?? defaultColor
if currentColor?.baseColor != .custom {
colors = colors.filter { $0 != .custom }
}
colorItems.append(contentsOf: colors.map { .color($0) }) colorItems.append(contentsOf: colors.map { .color($0) })
if let customColors = customColors {
colorItems.append(contentsOf: customColors.colors.map { .custom($0) })
} else {
if let currentColor = currentColor, currentColor.baseColor == .custom {
colorItems.append(.custom(currentColor))
}
}
return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in
arguments.selectAccentColor(color) arguments.selectAccentColor(color)
}, contextAction: { theme, color, node, gesture in }, contextAction: { theme, color, node, gesture in
arguments.colorContextAction(theme, color, node, gesture) arguments.colorContextAction(theme, color, node, gesture)
}, openColorPicker: { }, openColorPicker: { create in
arguments.openAccentColorPicker(currentTheme) arguments.openAccentColorPicker(currentTheme, create)
}, tag: ThemeSettingsEntryTag.accentColor) }, tag: ThemeSettingsEntryTag.accentColor)
case let .autoNightTheme(theme, text, value): case let .autoNightTheme(theme, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
@ -350,8 +366,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
}) })
case let .themeListHeader(theme, text): case let .themeListHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors, _): case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _):
return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, displayUnsupported: true, themeSpecificAccentColors: themeSpecificAccentColors, currentTheme: currentTheme, updatedTheme: { theme in return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, displayUnsupported: true, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, currentTheme: currentTheme, updatedTheme: { theme in
if case let .cloud(theme) = theme, theme.theme.file == nil { if case let .cloud(theme) = theme, theme.theme.file == nil {
if theme.theme.isCreator { if theme.theme.isCreator {
arguments.editTheme(theme) arguments.editTheme(theme)
@ -392,10 +408,15 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
entries.append(.themeListHeader(presentationData.theme, title)) entries.append(.themeListHeader(presentationData.theme, title))
entries.append(.chatPreview(presentationData.theme, presentationData.theme, presentationData.chatWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)])) entries.append(.chatPreview(presentationData.theme, presentationData.theme, presentationData.chatWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)]))
entries.append(.themeItem(presentationData.theme, presentationData.strings, availableThemes, themeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificAccentColors[themeReference.index])) var wallpaper: TelegramWallpaper?
if let accentColor = presentationThemeSettings.themeSpecificAccentColors[themeReference.index] {
wallpaper = presentationThemeSettings.themeSpecificChatWallpapers[themeReference.index &+ Int64(accentColor.index)]
}
entries.append(.themeItem(presentationData.theme, presentationData.strings, availableThemes, themeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificChatWallpapers, presentationThemeSettings.themeSpecificAccentColors[themeReference.index]))
if case let .builtin(theme) = themeReference { if case let .builtin(theme) = themeReference {
entries.append(.accentColor(presentationData.theme, themeReference, presentationThemeSettings.themeSpecificAccentColors[themeReference.index])) entries.append(.accentColor(presentationData.theme, themeReference, presentationThemeSettings.themeSpecificCustomColors[themeReference.index], presentationThemeSettings.themeSpecificAccentColors[themeReference.index]))
} }
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground)) entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
@ -476,38 +497,78 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in
selectThemeImpl?(theme) selectThemeImpl?(theme)
}, selectFontSize: { size in }, selectFontSize: { fontSize in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: size, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) return current.withUpdatedFontSize(fontSize)
}).start() }).start()
}, openWallpaperSettings: { }, openWallpaperSettings: {
pushControllerImpl?(ThemeGridController(context: context)) pushControllerImpl?(ThemeGridController(context: context))
}, selectAccentColor: { color in }, selectAccentColor: { color in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in var wallpaperSignal: Signal<TelegramWallpaper?, NoError> = .single(nil)
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered if let colorWallpaper = color?.wallpaper, case let .file(file) = colorWallpaper {
var currentTheme = current.theme wallpaperSignal = cachedWallpaper(account: context.account, slug: file.slug, settings: colorWallpaper.settings)
if autoNightModeTriggered { |> mapToSignal { cachedWallpaper in
currentTheme = current.automaticThemeSwitchSetting.theme if let wallpaper = cachedWallpaper?.wallpaper, case let .file(file) = wallpaper {
} let resource = file.file.resource
let representation = CachedPatternWallpaperRepresentation(color: file.settings.color ?? 0xd6e2ee, bottomColor: file.settings.bottomColor, intensity: file.settings.intensity ?? 50, rotation: file.settings.rotation)
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: currentTheme, accentColor: color?.color) else {
return current var data: Data?
} if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
data = maybeData
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers } else if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
var themeSpecificAccentColors = current.themeSpecificAccentColors data = maybeData
themeSpecificAccentColors[currentTheme.index] = color }
if case let .builtin(theme) = currentTheme, theme == .dayClassic || theme == .nightAccent { if let data = data {
if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin { context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
themeSpecificChatWallpapers[currentTheme.index] = nil return (context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true)
|> filter({ $0.complete })
|> take(1)
|> mapToSignal { _ -> Signal<TelegramWallpaper?, NoError> in
return .single(wallpaper)
})
} else {
return .single(nil)
}
} else {
return .single(nil)
} }
} }
}
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}).start() let _ = (wallpaperSignal
}, openAccentColorPicker: { themeReference in |> deliverOnMainQueue).start(next: { coloredWallpaper in
let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: themeReference)) let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered
var currentTheme = current.theme
if autoNightModeTriggered {
currentTheme = current.automaticThemeSwitchSetting.theme
}
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: currentTheme, accentColor: color?.color) else {
return current
}
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var themeSpecificAccentColors = current.themeSpecificAccentColors
themeSpecificAccentColors[currentTheme.index] = color
if case let .builtin(theme) = currentTheme, theme == .dayClassic || theme == .nightAccent {
if let wallpaper = coloredWallpaper, let color = color {
themeSpecificChatWallpapers[currentTheme.index &+ Int64(color.index)] = wallpaper
} else if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin {
themeSpecificChatWallpapers[currentTheme.index] = nil
if let color = color {
themeSpecificChatWallpapers[currentTheme.index &+ Int64(color.index)] = nil
}
}
}
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}).start()
})
}, openAccentColorPicker: { themeReference, create in
let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: themeReference, create: create))
pushControllerImpl?(controller) pushControllerImpl?(controller)
}, openAutoNightTheme: { }, openAutoNightTheme: {
pushControllerImpl?(themeAutoNightSettingsController(context: context)) pushControllerImpl?(themeAutoNightSettingsController(context: context))
@ -520,11 +581,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}) })
}, toggleLargeEmoji: { largeEmoji in }, toggleLargeEmoji: { largeEmoji in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: largeEmoji, disableAnimations: current.disableAnimations) return current.withUpdatedLargeEmoji(largeEmoji)
}).start() }).start()
}, disableAnimations: { value in }, disableAnimations: { disableAnimations in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: value) return current.withUpdatedDisableAnimations(disableAnimations)
}).start() }).start()
}, selectAppIcon: { name in }, selectAppIcon: { name in
currentAppIconName.set(name) currentAppIconName.set(name)
@ -540,7 +601,15 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}, themeContextAction: { isCurrent, reference, node, gesture in }, themeContextAction: { isCurrent, reference, node, gesture in
let _ = (context.sharedContext.accountManager.transaction { transaction -> (PresentationThemeAccentColor?, TelegramWallpaper?) in let _ = (context.sharedContext.accountManager.transaction { transaction -> (PresentationThemeAccentColor?, TelegramWallpaper?) in
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
return (settings.themeSpecificAccentColors[reference.index], settings.themeSpecificChatWallpapers[reference.index]) let accentColor = settings.themeSpecificAccentColors[reference.index]
var wallpaper: TelegramWallpaper?
if let accentColor = accentColor {
wallpaper = settings.themeSpecificChatWallpapers[reference.index &+ Int64(accentColor.index)]
}
if wallpaper == nil {
settings.themeSpecificChatWallpapers[reference.index]
}
return (accentColor, wallpaper)
} |> map { accentColor, wallpaper in } |> map { accentColor, wallpaper in
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors), wallpaper) return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors), wallpaper)
} }
@ -650,7 +719,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
items.append(.action(ContextMenuActionItem(text: strings.Theme_Context_ChangeColors, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) items.append(.action(ContextMenuActionItem(text: strings.Theme_Context_ChangeColors, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor)
}, action: { c, f in }, action: { c, f in
c.dismiss(completion: { c.dismiss(completion: {
let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: reference)) let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: reference, create: true))
pushControllerImpl?(controller) pushControllerImpl?(controller)
}) })
}))) })))
@ -667,14 +736,64 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let strings = presentationData.strings let strings = presentationData.strings
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, nil)) let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, nil))
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
let removable = accentColor?.accentColor != nil || accentColor?.bubbleColors != nil if let accentColor = accentColor, accentColor.baseColor == .custom {
if removable {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveThemeColor, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveThemeColor, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { c, f in }, action: { c, f in
c.dismiss(completion: { c.dismiss(completion: {
let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: reference)) let actionSheet = ActionSheetController(presentationData: presentationData)
pushControllerImpl?(controller) var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeColorConfirmation, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
let themeReference: PresentationThemeReference
if presentationData.autoNightModeTriggered {
themeReference = current.automaticThemeSwitchSetting.theme
} else {
themeReference = current.theme
}
var themeSpecificAccentColors = current.themeSpecificAccentColors
var themeSpecificCustomColors = current.themeSpecificCustomColors
var customColors = themeSpecificCustomColors[themeReference.index]?.colors ?? []
var updatedAccentColor: PresentationThemeAccentColor
if let index = customColors.firstIndex(where: { $0.index == accentColor.index }) {
if index > 0 {
updatedAccentColor = customColors[index - 1]
} else {
if case let .builtin(theme) = themeReference {
let updatedBaseColor: PresentationThemeBaseColor
switch theme {
case .dayClassic, .nightAccent:
updatedBaseColor = .gray
case .day:
updatedBaseColor = .black
case .night:
updatedBaseColor = .white
}
updatedAccentColor = PresentationThemeAccentColor(baseColor: updatedBaseColor)
} else {
updatedAccentColor = PresentationThemeAccentColor(baseColor: .blue)
}
}
customColors.remove(at: index)
} else {
updatedAccentColor = PresentationThemeAccentColor(baseColor: .blue)
}
themeSpecificAccentColors[themeReference.index] = updatedAccentColor
themeSpecificCustomColors[themeReference.index] = PresentationThemeCustomColors(colors: customColors)
return current.withUpdatedThemeSpecificCustomColors(themeSpecificCustomColors).withUpdatedThemeSpecificAccentColors(themeSpecificAccentColors)
}).start()
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
presentControllerImpl?(actionSheet, nil)
}) })
}))) })))
} }
@ -754,7 +873,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
return controller?.navigationController as? NavigationController return controller?.navigationController as? NavigationController
} }
presentCrossfadeControllerImpl = { [weak controller] in presentCrossfadeControllerImpl = { [weak controller] in
if let controller = controller, controller.isNodeLoaded { if let controller = controller, controller.isNodeLoaded, let navigationController = controller.navigationController as? NavigationController, navigationController.topViewController === controller {
var topOffset: CGFloat? var topOffset: CGFloat?
var bottomOffset: CGFloat? var bottomOffset: CGFloat?
var themeItemNode: ThemeSettingsThemeItemNode? var themeItemNode: ThemeSettingsThemeItemNode?
@ -830,25 +949,14 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
if case let .cloud(info) = theme { if case let .cloud(info) = theme {
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper)) updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper))
} }
return (context.sharedContext.accountManager.transaction { transaction -> Void in return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in if autoNightModeTriggered {
let current: PresentationThemeSettings var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
if let entry = entry as? PresentationThemeSettings { updatedAutomaticThemeSwitchSetting.theme = updatedTheme
current = entry return current.withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting)
} else { } else {
current = PresentationThemeSettings.defaultSettings return current.withUpdatedTheme(updatedTheme)
} }
var theme = current.theme
var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting
if autoNightModeTriggered {
automaticThemeSwitchSetting.theme = updatedTheme
} else {
theme = updatedTheme
}
return PresentationThemeSettings(theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
})
}) })
}).start() }).start()
} }

View File

@ -23,6 +23,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let accentColor: PresentationThemeAccentColor? let accentColor: PresentationThemeAccentColor?
var selected: Bool var selected: Bool
let theme: PresentationTheme let theme: PresentationTheme
let wallpaper: TelegramWallpaper?
var stableId: Int64 { var stableId: Int64 {
return self.themeReference.index return self.themeReference.index
@ -47,6 +48,9 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
if lhs.theme !== rhs.theme { if lhs.theme !== rhs.theme {
return false return false
} }
if lhs.wallpaper != rhs.wallpaper {
return false
}
return true return true
} }
@ -55,7 +59,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
} }
func item(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem { func item(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem {
return ThemeSettingsThemeIconItem(context: context, themeReference: self.themeReference, accentColor: self.accentColor, selected: self.selected, title: self.title, theme: self.theme, action: action, contextAction: contextAction) return ThemeSettingsThemeIconItem(context: context, themeReference: self.themeReference, accentColor: self.accentColor, selected: self.selected, title: self.title, theme: self.theme, wallpaper: self.wallpaper, action: action, contextAction: contextAction)
} }
} }
@ -67,16 +71,18 @@ private class ThemeSettingsThemeIconItem: ListViewItem {
let selected: Bool let selected: Bool
let title: String let title: String
let theme: PresentationTheme let theme: PresentationTheme
let wallpaper: TelegramWallpaper?
let action: (PresentationThemeReference) -> Void let action: (PresentationThemeReference) -> Void
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)? let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
public init(context: AccountContext, themeReference: PresentationThemeReference, accentColor: PresentationThemeAccentColor?, selected: Bool, title: String, theme: PresentationTheme, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) { public init(context: AccountContext, themeReference: PresentationThemeReference, accentColor: PresentationThemeAccentColor?, selected: Bool, title: String, theme: PresentationTheme, wallpaper: TelegramWallpaper?, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) {
self.context = context self.context = context
self.themeReference = themeReference self.themeReference = themeReference
self.accentColor = accentColor self.accentColor = accentColor
self.selected = selected self.selected = selected
self.title = title self.title = title
self.theme = theme self.theme = theme
self.wallpaper = wallpaper
self.action = action self.action = action
self.contextAction = contextAction self.contextAction = contextAction
} }
@ -248,7 +254,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
if currentItem?.themeReference != item.themeReference { if currentItem?.themeReference != item.themeReference {
updatedThemeReference = true updatedThemeReference = true
} }
if currentItem?.accentColor != item.accentColor { if currentItem == nil || currentItem?.accentColor != item.accentColor {
updatedAccentColor = true updatedAccentColor = true
} }
if currentItem?.theme !== item.theme { if currentItem?.theme !== item.theme {
@ -273,7 +279,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
strongSelf.containerNode.isGestureEnabled = false strongSelf.containerNode.isGestureEnabled = false
} else { } else {
if updatedThemeReference || updatedAccentColor { if updatedThemeReference || updatedAccentColor {
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: item.themeReference, accentColor: item.accentColor?.color, bubbleColors: item.accentColor?.plainBubbleColors)) strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: item.themeReference, color: item.accentColor, wallpaper: item.wallpaper))
} }
strongSelf.containerNode.isGestureEnabled = true strongSelf.containerNode.isGestureEnabled = true
} }
@ -325,18 +331,20 @@ class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
let themes: [PresentationThemeReference] let themes: [PresentationThemeReference]
let displayUnsupported: Bool let displayUnsupported: Bool
let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
let currentTheme: PresentationThemeReference let currentTheme: PresentationThemeReference
let updatedTheme: (PresentationThemeReference) -> Void let updatedTheme: (PresentationThemeReference) -> Void
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)? let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
let tag: ItemListItemTag? let tag: ItemListItemTag?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], displayUnsupported: Bool, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], currentTheme: PresentationThemeReference, updatedTheme: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) { init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], displayUnsupported: Bool, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], currentTheme: PresentationThemeReference, updatedTheme: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.themes = themes self.themes = themes
self.displayUnsupported = displayUnsupported self.displayUnsupported = displayUnsupported
self.themeSpecificAccentColors = themeSpecificAccentColors self.themeSpecificAccentColors = themeSpecificAccentColors
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
self.currentTheme = currentTheme self.currentTheme = currentTheme
self.updatedTheme = updatedTheme self.updatedTheme = updatedTheme
self.contextAction = contextAction self.contextAction = contextAction
@ -382,16 +390,17 @@ private struct ThemeSettingsThemeItemNodeTransition {
let deletions: [ListViewDeleteItem] let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem] let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem] let updates: [ListViewUpdateItem]
let crossfade: Bool
} }
private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeSettingsThemeEntry], to toEntries: [ThemeSettingsThemeEntry]) -> ThemeSettingsThemeItemNodeTransition { private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeSettingsThemeEntry], to toEntries: [ThemeSettingsThemeEntry], crossfade: Bool) -> ThemeSettingsThemeItemNodeTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: .Down) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: .Down) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action, contextAction: contextAction), directionHint: nil) }
return ThemeSettingsThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates) return ThemeSettingsThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade)
} }
private func ensureThemeVisible(listNode: ListView, themeReference: PresentationThemeReference, animated: Bool) -> Bool { private func ensureThemeVisible(listNode: ListView, themeReference: PresentationThemeReference, animated: Bool) -> Bool {
@ -476,8 +485,8 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
self.enqueuedTransitions.remove(at: 0) self.enqueuedTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions() var options = ListViewDeleteAndInsertOptions()
if self.initialized { if self.initialized && transition.crossfade {
options.insert(.AnimateInsertion) options.insert(.AnimateCrossfade)
} }
var scrollToItem: ListViewScrollToItem? var scrollToItem: ListViewScrollToItem?
@ -488,7 +497,8 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
} }
} }
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in
})
} }
func asyncLayout() -> (_ item: ThemeSettingsThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { func asyncLayout() -> (_ item: ThemeSettingsThemeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
@ -576,7 +586,11 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
} }
let title = themeDisplayName(strings: item.strings, reference: theme) let title = themeDisplayName(strings: item.strings, reference: theme)
let accentColor = item.themeSpecificAccentColors[theme.index] let accentColor = item.themeSpecificAccentColors[theme.index]
entries.append(ThemeSettingsThemeEntry(index: index, themeReference: theme, title: title, accentColor: accentColor, selected: item.currentTheme.index == theme.index, theme: item.theme)) var wallpaper: TelegramWallpaper?
if let accentColor = accentColor {
wallpaper = item.themeSpecificChatWallpapers[theme.index &+ Int64(accentColor.index)]
}
entries.append(ThemeSettingsThemeEntry(index: index, themeReference: theme, title: title, accentColor: accentColor, selected: item.currentTheme.index == theme.index, theme: item.theme, wallpaper: wallpaper))
index += 1 index += 1
} }
@ -586,7 +600,9 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
ensureThemeVisible(listNode: strongSelf.listNode, themeReference: themeReference, animated: true) ensureThemeVisible(listNode: strongSelf.listNode, themeReference: themeReference, animated: true)
} }
} }
let transition = preparedTransition(context: item.context, action: action, contextAction: item.contextAction, from: strongSelf.entries ?? [], to: entries) let previousEntries = strongSelf.entries ?? []
let crossfade = previousEntries.count != entries.count
let transition = preparedTransition(context: item.context, action: action, contextAction: item.contextAction, from: previousEntries, to: entries, crossfade: crossfade)
strongSelf.enqueueTransition(transition) strongSelf.enqueueTransition(transition)
strongSelf.entries = entries strongSelf.entries = entries

View File

@ -334,6 +334,7 @@ struct WallpaperColorPanelNodeState {
var secondColor: UIColor? var secondColor: UIColor?
var secondColorAvailable: Bool var secondColorAvailable: Bool
var rotateAvailable: Bool var rotateAvailable: Bool
var rotation: Int32
var preview: Bool var preview: Bool
} }
@ -385,7 +386,7 @@ final class WallpaperColorPanelNode: ASDisplayNode {
self.firstColorFieldNode = ColorInputFieldNode(theme: theme) self.firstColorFieldNode = ColorInputFieldNode(theme: theme)
self.secondColorFieldNode = ColorInputFieldNode(theme: theme) self.secondColorFieldNode = ColorInputFieldNode(theme: theme)
self.state = WallpaperColorPanelNodeState(selection: .first, firstColor: nil, secondColor: nil, secondColorAvailable: false, rotateAvailable: false, preview: false) self.state = WallpaperColorPanelNodeState(selection: .first, firstColor: nil, secondColor: nil, secondColorAvailable: false, rotateAvailable: false, rotation: 0, preview: false)
super.init() super.init()
@ -528,6 +529,7 @@ final class WallpaperColorPanelNode: ASDisplayNode {
let previousFirstColor = self.state.firstColor let previousFirstColor = self.state.firstColor
let previousSecondColor = self.state.secondColor let previousSecondColor = self.state.secondColor
let previousPreview = self.state.preview let previousPreview = self.state.preview
let previousRotation = self.state.rotation
self.state = f(self.state) self.state = f(self.state)
let firstColor: UIColor let firstColor: UIColor
@ -556,7 +558,7 @@ final class WallpaperColorPanelNode: ASDisplayNode {
if firstColorWasRemovable != self.firstColorFieldNode.isRemovable { if firstColorWasRemovable != self.firstColorFieldNode.isRemovable {
updateLayout = true updateLayout = true
} }
if updateLayout, let size = self.validLayout { if updateLayout, let size = self.validLayout {
switch self.state.selection { switch self.state.selection {
case .first: case .first:
@ -694,6 +696,17 @@ final class WallpaperColorPanelNode: ASDisplayNode {
transition.updateAlpha(node: self.swapButton, alpha: swapButtonAlpha) transition.updateAlpha(node: self.swapButton, alpha: swapButtonAlpha)
transition.updateAlpha(node: self.addButton, alpha: addButtonAlpha) transition.updateAlpha(node: self.addButton, alpha: addButtonAlpha)
func degreesToRadians(_ degrees: CGFloat) -> CGFloat
{
var degrees = degrees
if degrees >= 270.0 {
degrees = degrees - 360.0
}
return degrees * CGFloat.pi / 180.0
}
transition.updateTransformRotation(node: self.rotateButton, angle: degreesToRadians(CGFloat(self.state.rotation)), beginWithCurrentState: true, completion: nil)
self.firstColorFieldNode.isRemovable = self.state.secondColor != nil || (self.state.defaultColor != nil && self.state.firstColor != nil) self.firstColorFieldNode.isRemovable = self.state.secondColor != nil || (self.state.defaultColor != nil && self.state.firstColor != nil)
self.secondColorFieldNode.isRemovable = true self.secondColorFieldNode.isRemovable = true
@ -715,6 +728,15 @@ final class WallpaperColorPanelNode: ASDisplayNode {
@objc private func rotatePressed() { @objc private func rotatePressed() {
self.rotate?() self.rotate?()
self.updateState({ current in
var updated = current
var newRotation = updated.rotation + 45
if newRotation >= 360 {
newRotation = 0
}
updated.rotation = newRotation
return updated
})
} }
@objc private func swapPressed() { @objc private func swapPressed() {

View File

@ -374,12 +374,15 @@ public class WallpaperGalleryController: ViewController {
let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var wallpaper = wallpaper.isBasicallyEqual(to: strongSelf.presentationData.theme.chat.defaultWallpaper) ? nil : wallpaper var wallpaper = wallpaper.isBasicallyEqual(to: strongSelf.presentationData.theme.chat.defaultWallpaper) ? nil : wallpaper
let themeReference: PresentationThemeReference
if autoNightModeTriggered { if autoNightModeTriggered {
themeSpecificChatWallpapers[current.automaticThemeSwitchSetting.theme.index] = wallpaper themeReference = current.automaticThemeSwitchSetting.theme
} else { } else {
themeSpecificChatWallpapers[current.theme.index] = wallpaper themeReference = current.theme
} }
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) let accentColorIndex = current.themeSpecificAccentColors[themeReference.index]?.index ?? 0
themeSpecificChatWallpapers[themeReference.index &+ Int64(accentColorIndex)] = wallpaper
return current.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers)
}) |> deliverOnMainQueue).start(completed: { }) |> deliverOnMainQueue).start(completed: {
self?.dismiss(forceAway: true) self?.dismiss(forceAway: true)
}) })

View File

@ -190,6 +190,7 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit
return PresentationTheme( return PresentationTheme(
name: theme.name, name: theme.name,
index: theme.index,
referenceTheme: theme.referenceTheme, referenceTheme: theme.referenceTheme,
overallDarkAppearance: theme.overallDarkAppearance, overallDarkAppearance: theme.overallDarkAppearance,
intro: intro, intro: intro,
@ -515,6 +516,7 @@ public func makeDefaultDarkPresentationTheme(preview: Bool) -> PresentationTheme
return PresentationTheme( return PresentationTheme(
name: .builtin(.night), name: .builtin(.night),
index: PresentationThemeReference.builtin(.night).index,
referenceTheme: .night, referenceTheme: .night,
overallDarkAppearance: true, overallDarkAppearance: true,
intro: intro, intro: intro,

View File

@ -421,6 +421,7 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme
return PresentationTheme( return PresentationTheme(
name: theme.name, name: theme.name,
index: theme.index,
referenceTheme: theme.referenceTheme, referenceTheme: theme.referenceTheme,
overallDarkAppearance: theme.overallDarkAppearance, overallDarkAppearance: theme.overallDarkAppearance,
intro: intro, intro: intro,
@ -782,6 +783,7 @@ public func makeDefaultDarkTintedPresentationTheme(preview: Bool) -> Presentatio
return PresentationTheme( return PresentationTheme(
name: .builtin(.nightAccent), name: .builtin(.nightAccent),
index: PresentationThemeReference.builtin(.nightAccent).index,
referenceTheme: .nightAccent, referenceTheme: .nightAccent,
overallDarkAppearance: true, overallDarkAppearance: true,
intro: intro, intro: intro,

View File

@ -263,6 +263,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ac
return PresentationTheme( return PresentationTheme(
name: theme.name, name: theme.name,
index: theme.index,
referenceTheme: theme.referenceTheme, referenceTheme: theme.referenceTheme,
overallDarkAppearance: theme.overallDarkAppearance, overallDarkAppearance: theme.overallDarkAppearance,
intro: intro, intro: intro,
@ -705,6 +706,7 @@ public func makeDefaultDayPresentationTheme(serviceBackgroundColor: UIColor?, da
return PresentationTheme( return PresentationTheme(
name: .builtin(day ? .day : .dayClassic), name: .builtin(day ? .day : .dayClassic),
index: PresentationThemeReference.builtin(day ? .day : .dayClassic).index,
referenceTheme: day ? .day : .dayClassic, referenceTheme: day ? .day : .dayClassic,
overallDarkAppearance: false, overallDarkAppearance: false,
intro: intro, intro: intro,

View File

@ -42,13 +42,13 @@ public func makePresentationTheme(mediaBox: MediaBox, themeReference: Presentati
let defaultTheme = makeDefaultPresentationTheme(reference: reference, serviceBackgroundColor: serviceBackgroundColor, preview: preview) let defaultTheme = makeDefaultPresentationTheme(reference: reference, serviceBackgroundColor: serviceBackgroundColor, preview: preview)
theme = customizePresentationTheme(defaultTheme, editing: true, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper) theme = customizePresentationTheme(defaultTheme, editing: true, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper)
case let .local(info): case let .local(info):
if let path = mediaBox.completedResourcePath(info.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) { if let path = mediaBox.completedResourcePath(info.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, themeReference: themeReference, resolvedWallpaper: info.resolvedWallpaper) {
theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper) theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper)
} else { } else {
return nil return nil
} }
case let .cloud(info): case let .cloud(info):
if let file = info.theme.file, let path = mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) { if let file = info.theme.file, let path = mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, themeReference: themeReference, resolvedWallpaper: info.resolvedWallpaper) {
theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper) theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper)
} else { } else {
return nil return nil

View File

@ -239,11 +239,10 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager, s
autoNightModeTriggered = false autoNightModeTriggered = false
} }
let effectiveAccentColor = themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.color let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]
let effectiveBubbleColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.customBubbleColors let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors) ?? defaultPresentationTheme
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveAccentColor, bubbleColors: effectiveBubbleColors) ?? defaultPresentationTheme let effectiveChatWallpaper: TelegramWallpaper = (themeSettings.themeSpecificChatWallpapers[effectiveTheme.index &+ Int64(effectiveColors?.index ?? 0)] ?? themeSettings.themeSpecificChatWallpapers[effectiveTheme.index]) ?? theme.chat.defaultWallpaper
let effectiveChatWallpaper: TelegramWallpaper = themeSettings.themeSpecificChatWallpapers[effectiveTheme.index] ?? theme.chat.defaultWallpaper
let dateTimeFormat = currentDateTimeFormat() let dateTimeFormat = currentDateTimeFormat()
let stringsValue: PresentationStrings let stringsValue: PresentationStrings
@ -507,13 +506,14 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI
let contactSettings: ContactSynchronizationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings ?? ContactSynchronizationSettings.defaultSettings let contactSettings: ContactSynchronizationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings ?? ContactSynchronizationSettings.defaultSettings
let effectiveColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index]
let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index &+ Int64(effectiveColors?.index ?? 0)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index])
let currentWallpaper: TelegramWallpaper let currentWallpaper: TelegramWallpaper
if let themeSpecificWallpaper = themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index] { if let themeSpecificWallpaper = themeSpecificWallpaper {
currentWallpaper = themeSpecificWallpaper currentWallpaper = themeSpecificWallpaper
} else { } else {
let effectiveAccentColor = themeSettings.themeSpecificAccentColors[themeSettings.theme.index]?.color let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors) ?? defaultPresentationTheme
let effectiveBubbleColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index]?.customBubbleColors
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: effectiveAccentColor, bubbleColors: effectiveBubbleColors) ?? defaultPresentationTheme
currentWallpaper = theme.chat.defaultWallpaper currentWallpaper = theme.chat.defaultWallpaper
} }
@ -529,22 +529,25 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI
var effectiveTheme: PresentationThemeReference var effectiveTheme: PresentationThemeReference
var effectiveChatWallpaper: TelegramWallpaper = currentWallpaper var effectiveChatWallpaper: TelegramWallpaper = currentWallpaper
var switchedToNightModeWallpaper = false
if autoNightModeTriggered { if autoNightModeTriggered {
let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme
if let themeSpecificWallpaper = themeSettings.themeSpecificChatWallpapers[automaticTheme.index] { let effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index]
let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[automaticTheme.index &+ Int64(effectiveColors?.index ?? 0)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index])
if let themeSpecificWallpaper = themeSpecificWallpaper {
effectiveChatWallpaper = themeSpecificWallpaper effectiveChatWallpaper = themeSpecificWallpaper
switchedToNightModeWallpaper = true
} }
effectiveTheme = automaticTheme effectiveTheme = automaticTheme
} else { } else {
effectiveTheme = themeSettings.theme effectiveTheme = themeSettings.theme
} }
let effectiveAccentColor = themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.color let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]
let effectiveBubbleColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.customBubbleColors let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme
let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveAccentColor, bubbleColors: effectiveBubbleColors, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme if autoNightModeTriggered && !switchedToNightModeWallpaper {
if effectiveTheme != themeSettings.theme && themeSettings.themeSpecificChatWallpapers[effectiveTheme.index] == nil {
switch effectiveChatWallpaper { switch effectiveChatWallpaper {
case .builtin, .color, .gradient: case .builtin, .color, .gradient:
effectiveChatWallpaper = themeValue.chat.defaultWallpaper effectiveChatWallpaper = themeValue.chat.defaultWallpaper

View File

@ -1105,6 +1105,7 @@ public enum PresentationThemeName: Equatable {
public final class PresentationTheme: Equatable { public final class PresentationTheme: Equatable {
public let name: PresentationThemeName public let name: PresentationThemeName
public let index: Int64
public let referenceTheme: PresentationBuiltinThemeReference public let referenceTheme: PresentationBuiltinThemeReference
public let overallDarkAppearance: Bool public let overallDarkAppearance: Bool
public let intro: PresentationThemeIntro public let intro: PresentationThemeIntro
@ -1120,8 +1121,9 @@ public final class PresentationTheme: Equatable {
public let resourceCache: PresentationsResourceCache = PresentationsResourceCache() public let resourceCache: PresentationsResourceCache = PresentationsResourceCache()
public init(name: PresentationThemeName, referenceTheme: PresentationBuiltinThemeReference, overallDarkAppearance: Bool, intro: PresentationThemeIntro, passcode: PresentationThemePasscode, rootController: PresentationThemeRootController, list: PresentationThemeList, chatList: PresentationThemeChatList, chat: PresentationThemeChat, actionSheet: PresentationThemeActionSheet, contextMenu: PresentationThemeContextMenu, inAppNotification: PresentationThemeInAppNotification, preview: Bool = false) { public init(name: PresentationThemeName, index: Int64, referenceTheme: PresentationBuiltinThemeReference, overallDarkAppearance: Bool, intro: PresentationThemeIntro, passcode: PresentationThemePasscode, rootController: PresentationThemeRootController, list: PresentationThemeList, chatList: PresentationThemeChatList, chat: PresentationThemeChat, actionSheet: PresentationThemeActionSheet, contextMenu: PresentationThemeContextMenu, inAppNotification: PresentationThemeInAppNotification, preview: Bool = false) {
self.name = name self.name = name
self.index = index
self.referenceTheme = referenceTheme self.referenceTheme = referenceTheme
self.overallDarkAppearance = overallDarkAppearance self.overallDarkAppearance = overallDarkAppearance
self.intro = intro self.intro = intro
@ -1162,6 +1164,6 @@ public final class PresentationTheme: Equatable {
break break
} }
} }
return PresentationTheme(name: name.flatMap(PresentationThemeName.custom) ?? .custom(self.name.string), referenceTheme: self.referenceTheme, overallDarkAppearance: self.overallDarkAppearance, intro: self.intro, passcode: self.passcode, rootController: self.rootController, list: self.list, chatList: self.chatList, chat: self.chat.withUpdated(defaultWallpaper: defaultWallpaper), actionSheet: self.actionSheet, contextMenu: self.contextMenu, inAppNotification: self.inAppNotification) return PresentationTheme(name: name.flatMap(PresentationThemeName.custom) ?? .custom(self.name.string), index: self.index, referenceTheme: self.referenceTheme, overallDarkAppearance: self.overallDarkAppearance, intro: self.intro, passcode: self.passcode, rootController: self.rootController, list: self.list, chatList: self.chatList, chat: self.chat.withUpdated(defaultWallpaper: defaultWallpaper), actionSheet: self.actionSheet, contextMenu: self.contextMenu, inAppNotification: self.inAppNotification)
} }
} }

View File

@ -1723,12 +1723,17 @@ extension PresentationTheme: Codable {
referenceTheme = .dayClassic referenceTheme = .dayClassic
} }
let index: Int64
if let decoder = decoder as? PresentationThemeDecoding { if let decoder = decoder as? PresentationThemeDecoding {
let serviceBackgroundColor = decoder.serviceBackgroundColor ?? defaultServiceBackgroundColor let serviceBackgroundColor = decoder.serviceBackgroundColor ?? defaultServiceBackgroundColor
decoder.referenceTheme = makeDefaultPresentationTheme(reference: referenceTheme, serviceBackgroundColor: serviceBackgroundColor) decoder.referenceTheme = makeDefaultPresentationTheme(reference: referenceTheme, serviceBackgroundColor: serviceBackgroundColor)
index = decoder.reference?.index ?? arc4random64()
} else {
index = arc4random64()
} }
self.init(name: (try? values.decode(PresentationThemeName.self, forKey: .name)) ?? .custom("Untitled"), self.init(name: (try? values.decode(PresentationThemeName.self, forKey: .name)) ?? .custom("Untitled"),
index: index,
referenceTheme: referenceTheme, referenceTheme: referenceTheme,
overallDarkAppearance: (try? values.decode(Bool.self, forKey: .dark)) ?? false, overallDarkAppearance: (try? values.decode(Bool.self, forKey: .dark)) ?? false,
intro: try values.decode(PresentationThemeIntro.self, forKey: .intro), intro: try values.decode(PresentationThemeIntro.self, forKey: .intro),

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit import UIKit
import TelegramCore import TelegramCore
import SyncCore import SyncCore
import TelegramUIPreferences
public func encodePresentationTheme(_ theme: PresentationTheme) -> String? { public func encodePresentationTheme(_ theme: PresentationTheme) -> String? {
let encoding = PresentationThemeEncoding() let encoding = PresentationThemeEncoding()
@ -341,7 +342,7 @@ private class PresentationThemeDecodingLevel {
} }
} }
public func makePresentationTheme(data: Data, resolvedWallpaper: TelegramWallpaper? = nil) -> PresentationTheme? { public func makePresentationTheme(data: Data, themeReference: PresentationThemeReference? = nil, resolvedWallpaper: TelegramWallpaper? = nil) -> PresentationTheme? {
guard let string = String(data: data, encoding: .utf8) else { guard let string = String(data: data, encoding: .utf8) else {
return nil return nil
} }
@ -402,6 +403,7 @@ public func makePresentationTheme(data: Data, resolvedWallpaper: TelegramWallpap
} }
let decoder = PresentationThemeDecoding(referencing: topLevel.data) let decoder = PresentationThemeDecoding(referencing: topLevel.data)
decoder.reference = themeReference
decoder.resolvedWallpaper = resolvedWallpaper decoder.resolvedWallpaper = resolvedWallpaper
if let value = try? decoder.unbox(topLevel.data, as: PresentationTheme.self) { if let value = try? decoder.unbox(topLevel.data, as: PresentationTheme.self) {
return value return value
@ -418,6 +420,7 @@ class PresentationThemeDecoding: Decoder {
return [:] return [:]
} }
var reference: PresentationThemeReference?
var referenceTheme: PresentationTheme? var referenceTheme: PresentationTheme?
var serviceBackgroundColor: UIColor? var serviceBackgroundColor: UIColor?
var resolvedWallpaper: TelegramWallpaper? var resolvedWallpaper: TelegramWallpaper?

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 KiB

After

Width:  |  Height:  |  Size: 504 KiB

View File

@ -139,7 +139,7 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager {
theme = updatedTheme theme = updatedTheme
} }
return PresentationThemeSettings(theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) return PresentationThemeSettings(theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
}) })
}).start() }).start()
} }

View File

@ -117,6 +117,7 @@ final class WallpaperUploadManagerImpl: WallpaperUploadManager {
} }
if strongSelf.currentPresentationData?.theme.name == presentationData.theme.name { if strongSelf.currentPresentationData?.theme.name == presentationData.theme.name {
let autoNightModeTriggered = strongSelf.currentPresentationData?.autoNightModeTriggered ?? false
let _ = (updatePresentationThemeSettingsInteractively(accountManager: sharedContext.accountManager, { current in let _ = (updatePresentationThemeSettingsInteractively(accountManager: sharedContext.accountManager, { current in
let updatedWallpaper: TelegramWallpaper let updatedWallpaper: TelegramWallpaper
if let currentSettings = currentWallpaper.settings { if let currentSettings = currentWallpaper.settings {
@ -124,10 +125,17 @@ final class WallpaperUploadManagerImpl: WallpaperUploadManager {
} else { } else {
updatedWallpaper = wallpaper updatedWallpaper = wallpaper
} }
let themeReference: PresentationThemeReference
if autoNightModeTriggered {
themeReference = current.automaticThemeSwitchSetting.theme
} else {
themeReference = current.theme
}
let accentColorIndex = current.themeSpecificAccentColors[themeReference.index]?.index ?? 0
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[current.theme.index] = updatedWallpaper themeSpecificChatWallpapers[themeReference.index] = updatedWallpaper
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) themeSpecificChatWallpapers[themeReference.index &+ Int64(accentColorIndex)] = updatedWallpaper
return PresentationThemeSettings(theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificCustomColors: current.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
})).start() })).start()
} }
} }

View File

@ -314,6 +314,7 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable {
case black case black
case white case white
case custom case custom
case preset
public var color: UIColor { public var color: UIColor {
let value: UInt32 let value: UInt32
@ -340,29 +341,72 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable {
value = 0x000000 value = 0x000000
case .white: case .white:
value = 0xffffff value = 0xffffff
case .custom: case .custom, .preset:
return .clear return .clear
} }
return UIColor(rgb: value) return UIColor(rgb: value)
} }
} }
public struct PresentationThemeAccentColor: PostboxCoding, Equatable { public struct PresentationThemeCustomColors: PostboxCoding, Equatable {
public static func == (lhs: PresentationThemeAccentColor, rhs: PresentationThemeAccentColor) -> Bool { public static func == (lhs: PresentationThemeCustomColors, rhs: PresentationThemeCustomColors) -> Bool {
return lhs.baseColor == rhs.baseColor && lhs.accentColor == rhs.accentColor && lhs.bubbleColors?.0 == rhs.bubbleColors?.0 && lhs.bubbleColors?.1 == rhs.bubbleColors?.1 return lhs.colors == rhs.colors
} }
public var baseColor: PresentationThemeBaseColor public let colors: [PresentationThemeAccentColor]
public var accentColor: Int32?
public var bubbleColors: (Int32, Int32?)?
public init(baseColor: PresentationThemeBaseColor, accentColor: Int32? = nil, bubbleColors: (Int32, Int32?)? = nil) { public init(colors: [PresentationThemeAccentColor]) {
self.baseColor = baseColor self.colors = colors
self.accentColor = accentColor
self.bubbleColors = bubbleColors
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
if let colors = try? decoder.decodeObjectArrayWithCustomDecoderForKey("v", decoder: { decoder in
return PresentationThemeAccentColor(decoder: decoder)
}) {
self.colors = colors
} else {
self.colors = []
}
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObjectArray(self.colors, forKey: "v")
}
}
public struct PresentationThemeAccentColor: PostboxCoding, Equatable {
public static func == (lhs: PresentationThemeAccentColor, rhs: PresentationThemeAccentColor) -> Bool {
return lhs.index == rhs.index && lhs.baseColor == rhs.baseColor && lhs.accentColor == rhs.accentColor && lhs.bubbleColors?.0 == rhs.bubbleColors?.0 && lhs.bubbleColors?.1 == rhs.bubbleColors?.1
}
public var index: Int32
public var baseColor: PresentationThemeBaseColor
public var accentColor: Int32?
public var bubbleColors: (Int32, Int32?)?
public var wallpaper: TelegramWallpaper?
public init(baseColor: PresentationThemeBaseColor) {
if baseColor != .preset && baseColor != .custom {
self.index = baseColor.rawValue + 10
} else {
self.index = -1
}
self.baseColor = baseColor
self.accentColor = nil
self.bubbleColors = nil
self.wallpaper = nil
}
public init(index: Int32, baseColor: PresentationThemeBaseColor, accentColor: Int32? = nil, bubbleColors: (Int32, Int32?)? = nil, wallpaper: TelegramWallpaper? = nil) {
self.index = index
self.baseColor = baseColor
self.accentColor = accentColor
self.bubbleColors = bubbleColors
self.wallpaper = wallpaper
}
public init(decoder: PostboxDecoder) {
self.index = decoder.decodeInt32ForKey("i", orElse: -1)
self.baseColor = PresentationThemeBaseColor(rawValue: decoder.decodeInt32ForKey("b", orElse: 0)) ?? .blue self.baseColor = PresentationThemeBaseColor(rawValue: decoder.decodeInt32ForKey("b", orElse: 0)) ?? .blue
self.accentColor = decoder.decodeOptionalInt32ForKey("c") self.accentColor = decoder.decodeOptionalInt32ForKey("c")
if let bubbleTopColor = decoder.decodeOptionalInt32ForKey("bt") { if let bubbleTopColor = decoder.decodeOptionalInt32ForKey("bt") {
@ -377,6 +421,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable {
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.index, forKey: "i")
encoder.encodeInt32(self.baseColor.rawValue, forKey: "b") encoder.encodeInt32(self.baseColor.rawValue, forKey: "b")
if let value = self.accentColor { if let value = self.accentColor {
encoder.encodeInt32(value, forKey: "c") encoder.encodeInt32(value, forKey: "c")
@ -432,6 +477,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable {
public struct PresentationThemeSettings: PreferencesEntry { public struct PresentationThemeSettings: PreferencesEntry {
public var theme: PresentationThemeReference public var theme: PresentationThemeReference
public var themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] public var themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
public var themeSpecificCustomColors: [Int64: PresentationThemeCustomColors]
public var themeSpecificChatWallpapers: [Int64: TelegramWallpaper] public var themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
public var useSystemFont: Bool public var useSystemFont: Bool
public var fontSize: PresentationFontSize public var fontSize: PresentationFontSize
@ -471,16 +517,24 @@ public struct PresentationThemeSettings: PreferencesEntry {
resources.append(contentsOf: wallpaperResources(chatWallpaper)) resources.append(contentsOf: wallpaperResources(chatWallpaper))
} }
} }
for (_, colors) in self.themeSpecificCustomColors {
for color in colors.colors {
if let wallpaper = color.wallpaper {
resources.append(contentsOf: wallpaperResources(wallpaper))
}
}
}
return resources return resources
} }
public static var defaultSettings: PresentationThemeSettings { public static var defaultSettings: PresentationThemeSettings {
return PresentationThemeSettings(theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], useSystemFont: true, fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.night)), largeEmoji: true, disableAnimations: true) return PresentationThemeSettings(theme: .builtin(.dayClassic), themeSpecificAccentColors: [:], themeSpecificCustomColors: [:], themeSpecificChatWallpapers: [:], useSystemFont: true, fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .system, theme: .builtin(.night)), largeEmoji: true, disableAnimations: true)
} }
public init(theme: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], useSystemFont: Bool, fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, largeEmoji: Bool, disableAnimations: Bool) { public init(theme: PresentationThemeReference, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificCustomColors: [Int64: PresentationThemeCustomColors], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], useSystemFont: Bool, fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, largeEmoji: Bool, disableAnimations: Bool) {
self.theme = theme self.theme = theme
self.themeSpecificAccentColors = themeSpecificAccentColors self.themeSpecificAccentColors = themeSpecificAccentColors
self.themeSpecificCustomColors = themeSpecificCustomColors
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
self.useSystemFont = useSystemFont self.useSystemFont = useSystemFont
self.fontSize = fontSize self.fontSize = fontSize
@ -498,6 +552,12 @@ public struct PresentationThemeSettings: PreferencesEntry {
return TelegramWallpaper(decoder: decoder) return TelegramWallpaper(decoder: decoder)
}) })
self.themeSpecificCustomColors = decoder.decodeObjectDictionaryForKey("themeSpecificCustomColors", keyDecoder: { decoder in
return decoder.decodeInt64ForKey("k", orElse: 0)
}, valueDecoder: { decoder in
return PresentationThemeCustomColors(decoder: decoder)
})
self.themeSpecificAccentColors = decoder.decodeObjectDictionaryForKey("themeSpecificAccentColors", keyDecoder: { decoder in self.themeSpecificAccentColors = decoder.decodeObjectDictionaryForKey("themeSpecificAccentColors", keyDecoder: { decoder in
return decoder.decodeInt64ForKey("k", orElse: 0) return decoder.decodeInt64ForKey("k", orElse: 0)
}, valueDecoder: { decoder in }, valueDecoder: { decoder in
@ -516,6 +576,9 @@ public struct PresentationThemeSettings: PreferencesEntry {
encoder.encodeObjectDictionary(self.themeSpecificAccentColors, forKey: "themeSpecificAccentColors", keyEncoder: { key, encoder in encoder.encodeObjectDictionary(self.themeSpecificAccentColors, forKey: "themeSpecificAccentColors", keyEncoder: { key, encoder in
encoder.encodeInt64(key, forKey: "k") encoder.encodeInt64(key, forKey: "k")
}) })
encoder.encodeObjectDictionary(self.themeSpecificCustomColors, forKey: "themeSpecificCustomColors", keyEncoder: { key, encoder in
encoder.encodeInt64(key, forKey: "k")
})
encoder.encodeObjectDictionary(self.themeSpecificChatWallpapers, forKey: "themeSpecificChatWallpapers", keyEncoder: { key, encoder in encoder.encodeObjectDictionary(self.themeSpecificChatWallpapers, forKey: "themeSpecificChatWallpapers", keyEncoder: { key, encoder in
encoder.encodeInt64(key, forKey: "k") encoder.encodeInt64(key, forKey: "k")
}) })
@ -535,7 +598,43 @@ public struct PresentationThemeSettings: PreferencesEntry {
} }
public static func ==(lhs: PresentationThemeSettings, rhs: PresentationThemeSettings) -> Bool { public static func ==(lhs: PresentationThemeSettings, rhs: PresentationThemeSettings) -> Bool {
return lhs.theme == rhs.theme && lhs.themeSpecificAccentColors == rhs.themeSpecificAccentColors && lhs.themeSpecificChatWallpapers == rhs.themeSpecificChatWallpapers && lhs.useSystemFont == rhs.useSystemFont && 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.themeSpecificCustomColors == rhs.themeSpecificCustomColors && lhs.themeSpecificChatWallpapers == rhs.themeSpecificChatWallpapers && lhs.useSystemFont == rhs.useSystemFont && lhs.fontSize == rhs.fontSize && lhs.automaticThemeSwitchSetting == rhs.automaticThemeSwitchSetting && lhs.largeEmoji == rhs.largeEmoji && lhs.disableAnimations == rhs.disableAnimations
}
public func withUpdatedTheme(_ theme: PresentationThemeReference) -> PresentationThemeSettings {
return PresentationThemeSettings(theme: theme, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificCustomColors: self.themeSpecificCustomColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, useSystemFont: self.useSystemFont, fontSize: self.fontSize, automaticThemeSwitchSetting: self.automaticThemeSwitchSetting, largeEmoji: self.largeEmoji, disableAnimations: self.disableAnimations)
}
public func withUpdatedThemeSpecificAccentColors(_ themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]) -> PresentationThemeSettings {
return PresentationThemeSettings(theme: self.theme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificCustomColors: self.themeSpecificCustomColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, useSystemFont: self.useSystemFont, fontSize: self.fontSize, automaticThemeSwitchSetting: self.automaticThemeSwitchSetting, largeEmoji: self.largeEmoji, disableAnimations: self.disableAnimations)
}
public func withUpdatedThemeSpecificCustomColors(_ themeSpecificCustomColors: [Int64: PresentationThemeCustomColors]) -> PresentationThemeSettings {
return PresentationThemeSettings(theme: self.theme, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificCustomColors: themeSpecificCustomColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, useSystemFont: self.useSystemFont, fontSize: self.fontSize, automaticThemeSwitchSetting: self.automaticThemeSwitchSetting, largeEmoji: self.largeEmoji, disableAnimations: self.disableAnimations)
}
public func withUpdatedThemeSpecificChatWallpapers(_ themeSpecificChatWallpapers: [Int64: TelegramWallpaper]) -> PresentationThemeSettings {
return PresentationThemeSettings(theme: self.theme, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificCustomColors: self.themeSpecificCustomColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: self.useSystemFont, fontSize: self.fontSize, automaticThemeSwitchSetting: self.automaticThemeSwitchSetting, largeEmoji: self.largeEmoji, disableAnimations: self.disableAnimations)
}
public func withUpdatedUseSystemFont(_ useSystemFont: Bool) -> PresentationThemeSettings {
return PresentationThemeSettings(theme: self.theme, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificCustomColors: self.themeSpecificCustomColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, useSystemFont: useSystemFont, fontSize: self.fontSize, automaticThemeSwitchSetting: self.automaticThemeSwitchSetting, largeEmoji: self.largeEmoji, disableAnimations: self.disableAnimations)
}
public func withUpdatedFontSize(_ fontSize: PresentationFontSize) -> PresentationThemeSettings {
return PresentationThemeSettings(theme: self.theme, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificCustomColors: self.themeSpecificCustomColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, useSystemFont: self.useSystemFont, fontSize: fontSize, automaticThemeSwitchSetting: self.automaticThemeSwitchSetting, largeEmoji: self.largeEmoji, disableAnimations: self.disableAnimations)
}
public func withUpdatedAutomaticThemeSwitchSetting(_ automaticThemeSwitchSetting: AutomaticThemeSwitchSetting) -> PresentationThemeSettings {
return PresentationThemeSettings(theme: self.theme, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificCustomColors: self.themeSpecificCustomColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, useSystemFont: self.useSystemFont, fontSize: self.fontSize, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: self.largeEmoji, disableAnimations: self.disableAnimations)
}
public func withUpdatedLargeEmoji(_ largeEmoji: Bool) -> PresentationThemeSettings {
return PresentationThemeSettings(theme: self.theme, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificCustomColors: self.themeSpecificCustomColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, useSystemFont: self.useSystemFont, fontSize: self.fontSize, automaticThemeSwitchSetting: self.automaticThemeSwitchSetting, largeEmoji: largeEmoji, disableAnimations: self.disableAnimations)
}
public func withUpdatedDisableAnimations(_ disableAnimations: Bool) -> PresentationThemeSettings {
return PresentationThemeSettings(theme: self.theme, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificCustomColors: self.themeSpecificCustomColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, useSystemFont: self.useSystemFont, fontSize: self.fontSize, automaticThemeSwitchSetting: self.automaticThemeSwitchSetting, largeEmoji: self.largeEmoji, disableAnimations: disableAnimations)
} }
} }

View File

@ -1056,39 +1056,41 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef
} }
} }
public func themeIconImage(account: Account, accountManager: AccountManager, theme: PresentationThemeReference, accentColor: UIColor?, bubbleColors: (UIColor, UIColor)?) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { public func themeIconImage(account: Account, accountManager: AccountManager, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let colorsSignal: Signal<((UIColor, UIColor?), (UIColor, UIColor), (UIColor, UIColor), UIImage?), NoError> let colorsSignal: Signal<((UIColor, UIColor?), (UIColor, UIColor), (UIColor, UIColor), UIImage?, Int32?), NoError>
if case let .builtin(theme) = theme { if case let .builtin(theme) = theme {
let backgroundColor: UIColor
let incomingColor: UIColor let incomingColor: UIColor
let outgoingColor: (UIColor, UIColor) let outgoingColor: (UIColor, UIColor)
var accentColor = accentColor var accentColor = color?.color
var bubbleColors = color?.plainBubbleColors
var topBackgroundColor: UIColor
var bottomBackgroundColor: UIColor?
switch theme { switch theme {
case .dayClassic: case .dayClassic:
incomingColor = UIColor(rgb: 0xffffff) incomingColor = UIColor(rgb: 0xffffff)
if let accentColor = accentColor { if let accentColor = accentColor {
if let bubbleColors = bubbleColors { if let bubbleColors = bubbleColors {
backgroundColor = UIColor(rgb: 0xd6e2ee) topBackgroundColor = UIColor(rgb: 0xd6e2ee)
outgoingColor = bubbleColors outgoingColor = bubbleColors
} else { } else {
backgroundColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.867, brightness: 0.965) topBackgroundColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.867, brightness: 0.965)
let hsb = accentColor.hsb let hsb = accentColor.hsb
let bubbleColor = UIColor(hue: hsb.0, saturation: hsb.2 > 0.0 ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0) let bubbleColor = UIColor(hue: hsb.0, saturation: hsb.2 > 0.0 ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0)
outgoingColor = (bubbleColor, bubbleColor) outgoingColor = (bubbleColor, bubbleColor)
} }
} else { } else {
backgroundColor = UIColor(rgb: 0xd6e2ee) topBackgroundColor = UIColor(rgb: 0xd6e2ee)
outgoingColor = (UIColor(rgb: 0xe1ffc7), UIColor(rgb: 0xe1ffc7)) outgoingColor = (UIColor(rgb: 0xe1ffc7), UIColor(rgb: 0xe1ffc7))
} }
case .day: case .day:
backgroundColor = UIColor(rgb: 0xffffff) topBackgroundColor = UIColor(rgb: 0xffffff)
incomingColor = UIColor(rgb: 0xd5dde6) incomingColor = UIColor(rgb: 0xd5dde6)
if accentColor == nil { if accentColor == nil {
accentColor = UIColor(rgb: 0x007aff) accentColor = UIColor(rgb: 0x007aff)
} }
outgoingColor = bubbleColors ?? (accentColor!, accentColor!) outgoingColor = bubbleColors ?? (accentColor!, accentColor!)
case .night: case .night:
backgroundColor = UIColor(rgb: 0x000000) topBackgroundColor = UIColor(rgb: 0x000000)
incomingColor = UIColor(rgb: 0x1f1f1f) incomingColor = UIColor(rgb: 0x1f1f1f)
if accentColor == nil { if accentColor == nil {
accentColor = UIColor(rgb: 0x313131) accentColor = UIColor(rgb: 0x313131)
@ -1096,12 +1098,33 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
outgoingColor = bubbleColors ?? (accentColor!, accentColor!) outgoingColor = bubbleColors ?? (accentColor!, accentColor!)
case .nightAccent: case .nightAccent:
let accentColor = accentColor ?? UIColor(rgb: 0x007aff) let accentColor = accentColor ?? UIColor(rgb: 0x007aff)
backgroundColor = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18) topBackgroundColor = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18)
incomingColor = accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25) incomingColor = accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25)
let accentBubbleColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59) let accentBubbleColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59)
outgoingColor = bubbleColors ?? (accentBubbleColor, accentBubbleColor) outgoingColor = bubbleColors ?? (accentBubbleColor, accentBubbleColor)
} }
colorsSignal = .single(((backgroundColor, nil), (incomingColor, incomingColor), outgoingColor, nil))
var rotation: Int32?
if let wallpaper = wallpaper {
switch wallpaper {
case let .color(color):
topBackgroundColor = UIColor(rgb: UInt32(bitPattern: color))
case let .gradient(topColor, bottomColor, settings):
topBackgroundColor = UIColor(rgb: UInt32(bitPattern: topColor))
bottomBackgroundColor = UIColor(rgb: UInt32(bitPattern: bottomColor))
rotation = settings.rotation
case let .file(file):
if let color = file.settings.color {
topBackgroundColor = UIColor(rgb: UInt32(bitPattern: color))
bottomBackgroundColor = file.settings.bottomColor.flatMap { UIColor(rgb: UInt32(bitPattern: $0)) }
}
rotation = file.settings.rotation
default:
topBackgroundColor = UIColor(rgb: 0xd6e2ee)
}
}
colorsSignal = .single(((topBackgroundColor, bottomBackgroundColor), (incomingColor, incomingColor), outgoingColor, nil, rotation))
} else { } else {
var resource: MediaResource? var resource: MediaResource?
if case let .local(theme) = theme { if case let .local(theme) = theme {
@ -1111,9 +1134,10 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
} }
if let resource = resource { if let resource = resource {
colorsSignal = telegramThemeData(account: account, accountManager: accountManager, resource: resource, synchronousLoad: false) colorsSignal = telegramThemeData(account: account, accountManager: accountManager, resource: resource, synchronousLoad: false)
|> mapToSignal { data -> Signal<((UIColor, UIColor?), (UIColor, UIColor), (UIColor, UIColor), UIImage?), NoError> in |> mapToSignal { data -> Signal<((UIColor, UIColor?), (UIColor, UIColor), (UIColor, UIColor), UIImage?, Int32?), NoError> in
if let data = data, let theme = makePresentationTheme(data: data) { if let data = data, let theme = makePresentationTheme(data: data) {
var wallpaperSignal: Signal<((UIColor, UIColor?), (UIColor, UIColor), (UIColor, UIColor), UIImage?), NoError> = .complete() var wallpaperSignal: Signal<((UIColor, UIColor?), (UIColor, UIColor), (UIColor, UIColor), UIImage?, Int32?), NoError> = .complete()
var rotation: Int32?
let backgroundColor: (UIColor, UIColor?) let backgroundColor: (UIColor, UIColor?)
let incomingColor = (theme.chat.message.incoming.bubble.withoutWallpaper.fill, theme.chat.message.incoming.bubble.withoutWallpaper.gradientFill) let incomingColor = (theme.chat.message.incoming.bubble.withoutWallpaper.fill, theme.chat.message.incoming.bubble.withoutWallpaper.gradientFill)
let outgoingColor = (theme.chat.message.outgoing.bubble.withoutWallpaper.fill, theme.chat.message.outgoing.bubble.withoutWallpaper.gradientFill) let outgoingColor = (theme.chat.message.outgoing.bubble.withoutWallpaper.fill, theme.chat.message.outgoing.bubble.withoutWallpaper.gradientFill)
@ -1122,11 +1146,13 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
backgroundColor = (UIColor(rgb: 0xd6e2ee), nil) backgroundColor = (UIColor(rgb: 0xd6e2ee), nil)
case let .color(color): case let .color(color):
backgroundColor = (UIColor(rgb: UInt32(bitPattern: color)), nil) backgroundColor = (UIColor(rgb: UInt32(bitPattern: color)), nil)
case let .gradient(topColor, bottomColor, _): case let .gradient(topColor, bottomColor, settings):
backgroundColor = (UIColor(rgb: UInt32(bitPattern: topColor)), UIColor(rgb: UInt32(bitPattern: bottomColor))) backgroundColor = (UIColor(rgb: UInt32(bitPattern: topColor)), UIColor(rgb: UInt32(bitPattern: bottomColor)))
rotation = settings.rotation
case .image: case .image:
backgroundColor = (.black, nil) backgroundColor = (.black, nil)
case let .file(file): case let .file(file):
rotation = file.settings.rotation
if file.isPattern, let color = file.settings.color { if file.isPattern, let color = file.settings.color {
backgroundColor = (UIColor(rgb: UInt32(bitPattern: color)), file.settings.bottomColor.flatMap { UIColor(rgb: UInt32(bitPattern: $0)) }) backgroundColor = (UIColor(rgb: UInt32(bitPattern: color)), file.settings.bottomColor.flatMap { UIColor(rgb: UInt32(bitPattern: $0)) })
} else { } else {
@ -1138,7 +1164,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
var convertedRepresentations: [ImageRepresentationWithReference] = [] var convertedRepresentations: [ImageRepresentationWithReference] = []
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.file.resource), reference: .media(media: .standalone(media: file.file), resource: file.file.resource))) convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.file.resource), reference: .media(media: .standalone(media: file.file), resource: file.file.resource)))
return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|> mapToSignal { _, fullSizeData, complete -> Signal<((UIColor, UIColor?), (UIColor, UIColor), (UIColor, UIColor), UIImage?), NoError> in |> mapToSignal { _, fullSizeData, complete -> Signal<((UIColor, UIColor?), (UIColor, UIColor), (UIColor, UIColor), UIImage?, Int32?), NoError> in
guard complete, let fullSizeData = fullSizeData else { guard complete, let fullSizeData = fullSizeData else {
return .complete() return .complete()
} }
@ -1154,13 +1180,13 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
return accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true) return accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true)
|> mapToSignal { _ in |> mapToSignal { _ in
if let image = UIImage(data: fullSizeData) { if let image = UIImage(data: fullSizeData) {
return .single((backgroundColor, incomingColor, outgoingColor, image)) return .single((backgroundColor, incomingColor, outgoingColor, image, rotation))
} else { } else {
return .complete() return .complete()
} }
} }
} else if let image = UIImage(data: fullSizeData) { } else if let image = UIImage(data: fullSizeData) {
return .single((backgroundColor, incomingColor, outgoingColor, image)) return .single((backgroundColor, incomingColor, outgoingColor, image, rotation))
} else { } else {
return .complete() return .complete()
} }
@ -1170,7 +1196,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
} }
} }
} }
return .single((backgroundColor, incomingColor, outgoingColor, nil)) return .single((backgroundColor, incomingColor, outgoingColor, nil, rotation))
|> then(wallpaperSignal) |> then(wallpaperSignal)
} else { } else {
return .complete() return .complete()
@ -1192,7 +1218,14 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
var locations: [CGFloat] = [0.0, 1.0] var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB() let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! 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: drawingRect.height), options: CGGradientDrawingOptions()) c.saveGState()
if let rotation = colors.4 {
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
c.rotate(by: CGFloat(rotation) * CGFloat.pi / 180.0)
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
}
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: drawingRect.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
c.restoreGState()
} else { } else {
c.setFillColor(colors.0.0.cgColor) c.setFillColor(colors.0.0.cgColor)
c.fill(drawingRect) c.fill(drawingRect)