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.";
"Appearance.RemoveThemeColor" = "Remove Color";
"Appearance.RemoveThemeColor" = "Remove";
"Appearance.RemoveThemeColorConfirmation" = "Remove Color";
"WallpaperPreview.PatternTitle" = "Choose Pattern";
"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)

View File

@ -156,12 +156,15 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE
let _ = (updatePresentationThemeSettingsInteractively(accountManager: accountManager, { current in
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
let themeReference: PresentationThemeReference
if autoNightModeTriggered {
themeSpecificChatWallpapers[current.automaticThemeSwitchSetting.theme.index] = wallpaper
themeReference = current.automaticThemeSwitchSetting.theme
} 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()
}

View File

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

View File

@ -15,13 +15,13 @@ import MediaResources
private let randomBackgroundColors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e]
enum ThemeAccentColorControllerMode {
case colors(themeReference: PresentationThemeReference)
case colors(themeReference: PresentationThemeReference, create: Bool)
case background(themeReference: PresentationThemeReference)
case edit(theme: PresentationTheme, wallpaper: TelegramWallpaper?, defaultThemeReference: PresentationThemeReference?, create: Bool, completion: (PresentationTheme) -> Void)
var themeReference: PresentationThemeReference? {
switch self {
case let .colors(themeReference), let .background(themeReference):
case let .colors(themeReference, _), let .background(themeReference):
return themeReference
case let .edit(_, _, defaultThemeReference, _, _):
return defaultThemeReference
@ -37,6 +37,7 @@ final class ThemeAccentColorController: ViewController {
private let section: ThemeColorSection
private let initialBackgroundColor: UIColor?
private var presentationData: PresentationData
private var initialAccentColor: PresentationThemeAccentColor?
private var controllerNode: ThemeAccentColorControllerNode {
return self.displayNode as! ThemeAccentColorControllerNode
@ -133,6 +134,7 @@ final class ThemeAccentColorController: ViewController {
}, apply: { [weak self] state, serviceBackgroundColor in
if let strongSelf = self {
let context = strongSelf.context
let initialAccentColor = strongSelf.initialAccentColor
var coloredWallpaper: TelegramWallpaper?
if let backgroundColors = state.backgroundColors {
@ -198,6 +200,8 @@ final class ThemeAccentColorController: ViewController {
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var themeSpecificAccentColors = current.themeSpecificAccentColors
var themeSpecificCustomColors = current.themeSpecificCustomColors
var customColors = themeSpecificCustomColors[currentTheme.index]?.colors ?? []
var bubbleColors: (Int32, Int32?)?
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
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]
if let coloredWallpaper = 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
if let strongSelf = self {
strongSelf.completion?()
@ -248,7 +273,7 @@ final class ThemeAccentColorController: ViewController {
var initialWallpaper: TelegramWallpaper?
var backgroundColors: (UIColor, UIColor?)?
var patternWallpaper: TelegramWallpaper?
var patternIntensity: Int32 = 40
var patternIntensity: Int32 = 50
var motion = false
let messageColors: (UIColor, UIColor?)?
var defaultMessagesColor: UIColor?
@ -288,9 +313,16 @@ final class ThemeAccentColorController: ViewController {
}
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
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
} else {
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.previousPatternWallpaper = nil
self.patternWallpaper = nil
self.patternIntensity = 40
self.patternIntensity = 50
self.motion = false
self.defaultMessagesColor = nil
self.messagesColors = nil
@ -669,7 +669,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}
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)
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 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 motionAlpha: CGFloat = displayOptionButtons && hasMotion ? 1.0 : 0.0

View File

@ -68,7 +68,7 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
case settingInfo(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 {
switch self {
@ -186,8 +186,8 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColors):
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsThemeAccentColors) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors {
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsThemeChatWallpapers):
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
} else {
return false
@ -244,8 +244,8 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .themeHeader(theme, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors):
return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, displayUnsupported: false, themeSpecificAccentColors: themeSpecificAccentColors, currentTheme: currentTheme, updatedTheme: { theme in
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, themeSpecificChatWallpapers: themeSpecificChatWallpapers, currentTheme: currentTheme, updatedTheme: { theme in
arguments.updateTheme(theme)
}, contextAction: nil)
}
@ -313,7 +313,7 @@ private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, s
break
case .system, .timeBased, .brightness:
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

View File

@ -182,8 +182,16 @@ final class ThemeGridController: ViewController {
let presentationData = strongSelf.presentationData
let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
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
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()
break
}
@ -233,18 +241,8 @@ final class ThemeGridController: ViewController {
let _ = resetWallpapers(account: strongSelf.context.account).start(completed: { [weak self, weak controller] in
let presentationData = strongSelf.presentationData
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> Void in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
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)
})
let _ = updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
return current.withUpdatedThemeSpecificChatWallpapers([:])
}).start()
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 _ = saveThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme).start()
}
return context.sharedContext.accountManager.transaction { transaction -> Void in
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
let current = entry as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
themeSpecificChatWallpapers[updatedTheme.index] = nil
var theme = current.theme
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current -> PresentationThemeSettings in
var updated: PresentationThemeSettings
if autoNightModeTriggered {
var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting
if autoNightModeTriggered {
automaticThemeSwitchSetting.theme = updatedTheme
} else {
theme = updatedTheme
}
automaticThemeSwitchSetting.theme = updatedTheme
updated = current.withUpdatedAutomaticThemeSwitchSetting(automaticThemeSwitchSetting)
} else {
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)?

View File

@ -7,74 +7,392 @@ import TelegramCore
import SyncCore
import TelegramPresentationData
import TelegramUIPreferences
import MergeLists
import ItemListUI
import ContextUI
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
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let fillColor = color.color
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.setStrokeColor(color.cgColor)
context.setLineWidth(2.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)
context.strokeEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0))
})
}
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? {
return generateImage(CGSize(width: 42.0, height: 42.0), rotatedContext: { size, context in
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 {
case `default`
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 {
@ -119,10 +506,10 @@ class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
let currentColor: PresentationThemeAccentColor?
let updated: (PresentationThemeAccentColor?) -> Void
let contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?
let openColorPicker: () -> Void
let openColorPicker: (Bool) -> Void
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.themeReference = themeReference
self.colors = colors
@ -168,66 +555,39 @@ class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
}
}
private final class ThemeSettingsAccentColorNode : ASDisplayNode {
private let containerNode: ContextControllerSourceNode
private let iconNode: ASImageNode
private var action: (() -> Void)?
private var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
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 struct ThemeSettingsAccentColorItemNodeTransition {
let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem]
let crossfade: Bool
}
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)
private let textFont = Font.regular(11.0)
private let itemSize = Font.regular(11.0)
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 func ensureColorVisible(listNode: ListView, accentColor: PresentationThemeAccentColor?, animated: Bool) -> Bool {
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 {
private let containerNode: ASDisplayNode
@ -237,9 +597,10 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
private let maskNode: ASImageNode
private var snapshotView: UIView?
private let scrollNode: ASScrollNode
private var colorNodes: [ThemeSettingsAccentColorNode] = []
private let customNode: HighlightableButtonNode
private let listNode: ListView
private var entries: [ThemeSettingsColorEntry]?
private var enqueuedTransitions: [ThemeSettingsAccentColorItemNodeTransition] = []
private var initialized = false
private var item: ThemeSettingsAccentColorItem?
private var layoutParams: ListViewItemLayoutParams?
@ -262,40 +623,53 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
self.maskNode = ASImageNode()
self.scrollNode = ASScrollNode()
self.customNode = HighlightableButtonNode()
self.listNode = ListView()
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.containerNode)
self.customNode.setImage(generateCustomSwatchImage(), for: .normal)
self.customNode.addTarget(self, action: #selector(customPressed), forControlEvents: .touchUpInside)
self.addSubnode(self.scrollNode)
self.scrollNode.addSubnode(self.customNode)
self.addSubnode(self.listNode)
}
override func didLoad() {
super.didLoad()
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true
}
@objc func customPressed() {
self.item?.openColorPicker()
}
private func enqueueTransition(_ transition: ThemeSettingsAccentColorItemNodeTransition) {
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 {
self.scrollNode.view.scrollRectToVisible(frame, animated: animated)
if let _ = self.item {
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) {
let currentItem = self.item
@ -320,7 +694,6 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.item = item
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.topStripeNode.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.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
let nodeSize = CGSize(width: 40.0, height: 40.0)
var nodeOffset = nodeInset
strongSelf.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width)
strongSelf.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0)
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 selectedNode: ThemeSettingsAccentColorNode?
var i = 0
var entries: [ThemeSettingsColorEntry] = []
var index: Int = 0
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 {
case .default:
selected = item.currentColor == nil
accentColor = PresentationThemeAccentColor(baseColor: .blue, accentColor: 0x007ee5, bubbleColors: (0xe1ffc7, nil))
isDefault = true
let selected = item.currentColor == nil
entries.append(.color(index, item.themeReference, nil, selected))
case let .color(color):
selected = item.currentColor?.baseColor == color
let selected = item.currentColor?.baseColor == color
let accentColor: PresentationThemeAccentColor
if let currentColor = item.currentColor, selected {
accentColor = currentColor
} 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))
}
if selected {
selectedNode = imageNode
}
index += 1
}
entries.append(.picker)
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
let action: (PresentationThemeAccentColor?, Bool) -> Void = { [weak self] color, selected in
if let strongSelf = self, let item = strongSelf.item {
if selected {
item.openColorPicker()
item.openColorPicker(color?.baseColor != .custom)
} else {
item.updated(itemColor)
item.updated(color)
}
if let imageNode = imageNode {
self?.scrollToNode(imageNode, animated: true)
}
}, contextAction: item.contextAction)
imageNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 10.0), size: nodeSize)
nodeOffset += nodeSize.width + 18.0
i += 1
ensureColorVisible(listNode: strongSelf.listNode, accentColor: color, animated: true)
}
}
let contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)? = { [weak item] color, node, gesture in
if let strongSelf = self, let item = strongSelf.item {
item.contextAction?(item.themeReference, color, node, gesture)
}
}
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() {
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)
}
strongSelf.entries = entries
}
})
}

View File

@ -11,6 +11,7 @@ import ItemListUI
import PresentationDataUtils
import AlertUI
import PresentationDataUtils
import MediaResources
import WallpaperResources
import ShareController
import AccountContext
@ -72,7 +73,7 @@ private final class ThemeSettingsControllerArguments {
let selectFontSize: (PresentationFontSize) -> Void
let openWallpaperSettings: () -> Void
let selectAccentColor: (PresentationThemeAccentColor?) -> Void
let openAccentColorPicker: (PresentationThemeReference) -> Void
let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
let openAutoNightTheme: () -> Void
let openTextSize: () -> Void
let toggleLargeEmoji: (Bool) -> Void
@ -82,7 +83,7 @@ private final class ThemeSettingsControllerArguments {
let themeContextAction: (Bool, PresentationThemeReference, 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.selectTheme = selectTheme
self.selectFontSize = selectFontSize
@ -132,10 +133,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case fontSize(PresentationTheme, PresentationFontSize)
case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeAccentColor?)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeCustomColors?, PresentationThemeAccentColor?)
case autoNightTheme(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 iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?)
case otherHeader(PresentationTheme, String)
@ -207,8 +208,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .accentColor(lhsTheme, lhsCurrentTheme, lhsColor):
if case let .accentColor(rhsTheme, rhsCurrentTheme, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsColor == rhsColor {
case let .accentColor(lhsTheme, lhsCurrentTheme, lhsCustomColors, lhsColor):
if case let .accentColor(rhsTheme, rhsCurrentTheme, rhsCustomColors, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsCustomColors == rhsCustomColors, lhsColor == rhsColor {
return true
} else {
return false
@ -231,8 +232,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsCurrentColor):
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsCurrentTheme, rhsThemeAccentColors, rhsCurrentColor) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors, lhsCurrentColor == rhsCurrentColor {
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsThemeSpecificChatWallpapers, lhsCurrentColor):
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
} else {
return false
@ -307,14 +308,25 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openWallpaperSettings()
})
case let .accentColor(theme, currentTheme, color):
case let .accentColor(theme, currentTheme, customColors, color):
var colorItems: [ThemeSettingsAccentColor] = []
var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue)
var colors = PresentationThemeBaseColor.allCases
colors = colors.filter { $0 != .custom && $0 != .preset }
if case let .builtin(name) = currentTheme {
if name == .dayClassic {
colorItems.append(.default)
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 {
colors = colors.filter { $0 != .black }
@ -327,18 +339,22 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
}
}
let currentColor = color ?? defaultColor
if currentColor?.baseColor != .custom {
colors = colors.filter { $0 != .custom }
}
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
arguments.selectAccentColor(color)
}, contextAction: { theme, color, node, gesture in
arguments.colorContextAction(theme, color, node, gesture)
}, openColorPicker: {
arguments.openAccentColorPicker(currentTheme)
}, openColorPicker: { create in
arguments.openAccentColorPicker(currentTheme, create)
}, tag: ThemeSettingsEntryTag.accentColor)
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: {
@ -350,8 +366,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
})
case let .themeListHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors, _):
return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, displayUnsupported: true, themeSpecificAccentColors: themeSpecificAccentColors, currentTheme: currentTheme, updatedTheme: { theme in
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, themeSpecificChatWallpapers: themeSpecificChatWallpapers, currentTheme: currentTheme, updatedTheme: { theme in
if case let .cloud(theme) = theme, theme.theme.file == nil {
if theme.theme.isCreator {
arguments.editTheme(theme)
@ -392,10 +408,15 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
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(.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 {
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))
@ -476,38 +497,78 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in
selectThemeImpl?(theme)
}, selectFontSize: { size in
}, selectFontSize: { fontSize 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()
}, openWallpaperSettings: {
pushControllerImpl?(ThemeGridController(context: context))
}, selectAccentColor: { color in
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
}
var wallpaperSignal: Signal<TelegramWallpaper?, NoError> = .single(nil)
if let colorWallpaper = color?.wallpaper, case let .file(file) = colorWallpaper {
wallpaperSignal = cachedWallpaper(account: context.account, slug: file.slug, settings: colorWallpaper.settings)
|> mapToSignal { cachedWallpaper in
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
} else if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
data = maybeData
}
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 = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin {
themeSpecificChatWallpapers[currentTheme.index] = nil
if let data = data {
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
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()
}, openAccentColorPicker: { themeReference in
let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: themeReference))
let _ = (wallpaperSignal
|> deliverOnMainQueue).start(next: { coloredWallpaper in
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)
}, openAutoNightTheme: {
pushControllerImpl?(themeAutoNightSettingsController(context: context))
@ -520,11 +581,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})
}, toggleLargeEmoji: { largeEmoji 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()
}, disableAnimations: { value in
}, disableAnimations: { disableAnimations 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()
}, selectAppIcon: { name in
currentAppIconName.set(name)
@ -540,7 +601,15 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}, themeContextAction: { isCurrent, reference, node, gesture in
let _ = (context.sharedContext.accountManager.transaction { transaction -> (PresentationThemeAccentColor?, TelegramWallpaper?) in
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
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)
}, action: { c, f in
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)
})
})))
@ -668,13 +737,63 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, nil))
var items: [ContextMenuItem] = []
let removable = accentColor?.accentColor != nil || accentColor?.bubbleColors != nil
if removable {
if let accentColor = accentColor, accentColor.baseColor == .custom {
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
c.dismiss(completion: {
let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: reference))
pushControllerImpl?(controller)
let actionSheet = ActionSheetController(presentationData: presentationData)
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
}
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 bottomOffset: CGFloat?
var themeItemNode: ThemeSettingsThemeItemNode?
@ -830,25 +949,14 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
if case let .cloud(info) = theme {
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper))
}
return (context.sharedContext.accountManager.transaction { transaction -> Void in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
let current: PresentationThemeSettings
if let entry = entry as? PresentationThemeSettings {
current = entry
} else {
current = PresentationThemeSettings.defaultSettings
}
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)
})
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
if autoNightModeTriggered {
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
updatedAutomaticThemeSwitchSetting.theme = updatedTheme
return current.withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting)
} else {
return current.withUpdatedTheme(updatedTheme)
}
})
}).start()
}

View File

@ -23,6 +23,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let accentColor: PresentationThemeAccentColor?
var selected: Bool
let theme: PresentationTheme
let wallpaper: TelegramWallpaper?
var stableId: Int64 {
return self.themeReference.index
@ -47,6 +48,9 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
if lhs.theme !== rhs.theme {
return false
}
if lhs.wallpaper != rhs.wallpaper {
return false
}
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 {
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 title: String
let theme: PresentationTheme
let wallpaper: TelegramWallpaper?
let action: (PresentationThemeReference) -> 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.themeReference = themeReference
self.accentColor = accentColor
self.selected = selected
self.title = title
self.theme = theme
self.wallpaper = wallpaper
self.action = action
self.contextAction = contextAction
}
@ -248,7 +254,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
if currentItem?.themeReference != item.themeReference {
updatedThemeReference = true
}
if currentItem?.accentColor != item.accentColor {
if currentItem == nil || currentItem?.accentColor != item.accentColor {
updatedAccentColor = true
}
if currentItem?.theme !== item.theme {
@ -273,7 +279,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
strongSelf.containerNode.isGestureEnabled = false
} else {
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
}
@ -325,18 +331,20 @@ class ThemeSettingsThemeItem: ListViewItem, ItemListItem {
let themes: [PresentationThemeReference]
let displayUnsupported: Bool
let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
let themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
let currentTheme: PresentationThemeReference
let updatedTheme: (PresentationThemeReference) -> Void
let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?
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.theme = theme
self.strings = strings
self.themes = themes
self.displayUnsupported = displayUnsupported
self.themeSpecificAccentColors = themeSpecificAccentColors
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
self.currentTheme = currentTheme
self.updatedTheme = updatedTheme
self.contextAction = contextAction
@ -382,16 +390,17 @@ private struct ThemeSettingsThemeItemNodeTransition {
let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem]
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 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 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 {
@ -476,8 +485,8 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
self.enqueuedTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions()
if self.initialized {
options.insert(.AnimateInsertion)
if self.initialized && transition.crossfade {
options.insert(.AnimateCrossfade)
}
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) {
@ -576,7 +586,11 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
}
let title = themeDisplayName(strings: item.strings, reference: theme)
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
}
@ -586,7 +600,9 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
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.entries = entries

View File

@ -334,6 +334,7 @@ struct WallpaperColorPanelNodeState {
var secondColor: UIColor?
var secondColorAvailable: Bool
var rotateAvailable: Bool
var rotation: Int32
var preview: Bool
}
@ -385,7 +386,7 @@ final class WallpaperColorPanelNode: ASDisplayNode {
self.firstColorFieldNode = 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()
@ -528,6 +529,7 @@ final class WallpaperColorPanelNode: ASDisplayNode {
let previousFirstColor = self.state.firstColor
let previousSecondColor = self.state.secondColor
let previousPreview = self.state.preview
let previousRotation = self.state.rotation
self.state = f(self.state)
let firstColor: UIColor
@ -694,6 +696,17 @@ final class WallpaperColorPanelNode: ASDisplayNode {
transition.updateAlpha(node: self.swapButton, alpha: swapButtonAlpha)
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.secondColorFieldNode.isRemovable = true
@ -715,6 +728,15 @@ final class WallpaperColorPanelNode: ASDisplayNode {
@objc private func rotatePressed() {
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() {

View File

@ -374,12 +374,15 @@ public class WallpaperGalleryController: ViewController {
let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var wallpaper = wallpaper.isBasicallyEqual(to: strongSelf.presentationData.theme.chat.defaultWallpaper) ? nil : wallpaper
let themeReference: PresentationThemeReference
if autoNightModeTriggered {
themeSpecificChatWallpapers[current.automaticThemeSwitchSetting.theme.index] = wallpaper
themeReference = current.automaticThemeSwitchSetting.theme
} 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: {
self?.dismiss(forceAway: true)
})

View File

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

View File

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

View File

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

View File

@ -42,13 +42,13 @@ public func makePresentationTheme(mediaBox: MediaBox, themeReference: Presentati
let defaultTheme = makeDefaultPresentationTheme(reference: reference, serviceBackgroundColor: serviceBackgroundColor, preview: preview)
theme = customizePresentationTheme(defaultTheme, editing: true, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper)
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)
} else {
return nil
}
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)
} else {
return nil

View File

@ -239,11 +239,10 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager, s
autoNightModeTriggered = false
}
let effectiveAccentColor = themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.color
let effectiveBubbleColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.customBubbleColors
let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]
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] ?? theme.chat.defaultWallpaper
let effectiveChatWallpaper: TelegramWallpaper = (themeSettings.themeSpecificChatWallpapers[effectiveTheme.index &+ Int64(effectiveColors?.index ?? 0)] ?? themeSettings.themeSpecificChatWallpapers[effectiveTheme.index]) ?? theme.chat.defaultWallpaper
let dateTimeFormat = currentDateTimeFormat()
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 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
if let themeSpecificWallpaper = themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index] {
if let themeSpecificWallpaper = themeSpecificWallpaper {
currentWallpaper = themeSpecificWallpaper
} else {
let effectiveAccentColor = themeSettings.themeSpecificAccentColors[themeSettings.theme.index]?.color
let effectiveBubbleColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index]?.customBubbleColors
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: effectiveAccentColor, bubbleColors: effectiveBubbleColors) ?? defaultPresentationTheme
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors) ?? defaultPresentationTheme
currentWallpaper = theme.chat.defaultWallpaper
}
@ -529,22 +529,25 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI
var effectiveTheme: PresentationThemeReference
var effectiveChatWallpaper: TelegramWallpaper = currentWallpaper
var switchedToNightModeWallpaper = false
if autoNightModeTriggered {
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
switchedToNightModeWallpaper = true
}
effectiveTheme = automaticTheme
} else {
effectiveTheme = themeSettings.theme
}
let effectiveAccentColor = themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.color
let effectiveBubbleColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]?.customBubbleColors
let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]
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 effectiveTheme != themeSettings.theme && themeSettings.themeSpecificChatWallpapers[effectiveTheme.index] == nil {
if autoNightModeTriggered && !switchedToNightModeWallpaper {
switch effectiveChatWallpaper {
case .builtin, .color, .gradient:
effectiveChatWallpaper = themeValue.chat.defaultWallpaper

View File

@ -1105,6 +1105,7 @@ public enum PresentationThemeName: Equatable {
public final class PresentationTheme: Equatable {
public let name: PresentationThemeName
public let index: Int64
public let referenceTheme: PresentationBuiltinThemeReference
public let overallDarkAppearance: Bool
public let intro: PresentationThemeIntro
@ -1120,8 +1121,9 @@ public final class PresentationTheme: Equatable {
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.index = index
self.referenceTheme = referenceTheme
self.overallDarkAppearance = overallDarkAppearance
self.intro = intro
@ -1162,6 +1164,6 @@ public final class PresentationTheme: Equatable {
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
}
let index: Int64
if let decoder = decoder as? PresentationThemeDecoding {
let serviceBackgroundColor = decoder.serviceBackgroundColor ?? defaultServiceBackgroundColor
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"),
index: index,
referenceTheme: referenceTheme,
overallDarkAppearance: (try? values.decode(Bool.self, forKey: .dark)) ?? false,
intro: try values.decode(PresentationThemeIntro.self, forKey: .intro),

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import TelegramCore
import SyncCore
import TelegramUIPreferences
public func encodePresentationTheme(_ theme: PresentationTheme) -> String? {
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 {
return nil
}
@ -402,6 +403,7 @@ public func makePresentationTheme(data: Data, resolvedWallpaper: TelegramWallpap
}
let decoder = PresentationThemeDecoding(referencing: topLevel.data)
decoder.reference = themeReference
decoder.resolvedWallpaper = resolvedWallpaper
if let value = try? decoder.unbox(topLevel.data, as: PresentationTheme.self) {
return value
@ -418,6 +420,7 @@ class PresentationThemeDecoding: Decoder {
return [:]
}
var reference: PresentationThemeReference?
var referenceTheme: PresentationTheme?
var serviceBackgroundColor: UIColor?
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
}
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()
}

View File

@ -117,6 +117,7 @@ final class WallpaperUploadManagerImpl: WallpaperUploadManager {
}
if strongSelf.currentPresentationData?.theme.name == presentationData.theme.name {
let autoNightModeTriggered = strongSelf.currentPresentationData?.autoNightModeTriggered ?? false
let _ = (updatePresentationThemeSettingsInteractively(accountManager: sharedContext.accountManager, { current in
let updatedWallpaper: TelegramWallpaper
if let currentSettings = currentWallpaper.settings {
@ -124,10 +125,17 @@ final class WallpaperUploadManagerImpl: WallpaperUploadManager {
} else {
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
themeSpecificChatWallpapers[current.theme.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] = updatedWallpaper
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()
}
}

View File

@ -314,6 +314,7 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable {
case black
case white
case custom
case preset
public var color: UIColor {
let value: UInt32
@ -340,29 +341,72 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable {
value = 0x000000
case .white:
value = 0xffffff
case .custom:
case .custom, .preset:
return .clear
}
return UIColor(rgb: value)
}
}
public struct PresentationThemeAccentColor: PostboxCoding, Equatable {
public static func == (lhs: PresentationThemeAccentColor, rhs: PresentationThemeAccentColor) -> Bool {
return lhs.baseColor == rhs.baseColor && lhs.accentColor == rhs.accentColor && lhs.bubbleColors?.0 == rhs.bubbleColors?.0 && lhs.bubbleColors?.1 == rhs.bubbleColors?.1
public struct PresentationThemeCustomColors: PostboxCoding, Equatable {
public static func == (lhs: PresentationThemeCustomColors, rhs: PresentationThemeCustomColors) -> Bool {
return lhs.colors == rhs.colors
}
public var baseColor: PresentationThemeBaseColor
public var accentColor: Int32?
public var bubbleColors: (Int32, Int32?)?
public let colors: [PresentationThemeAccentColor]
public init(baseColor: PresentationThemeBaseColor, accentColor: Int32? = nil, bubbleColors: (Int32, Int32?)? = nil) {
self.baseColor = baseColor
self.accentColor = accentColor
self.bubbleColors = bubbleColors
public init(colors: [PresentationThemeAccentColor]) {
self.colors = colors
}
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.accentColor = decoder.decodeOptionalInt32ForKey("c")
if let bubbleTopColor = decoder.decodeOptionalInt32ForKey("bt") {
@ -377,6 +421,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable {
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.index, forKey: "i")
encoder.encodeInt32(self.baseColor.rawValue, forKey: "b")
if let value = self.accentColor {
encoder.encodeInt32(value, forKey: "c")
@ -432,6 +477,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable {
public struct PresentationThemeSettings: PreferencesEntry {
public var theme: PresentationThemeReference
public var themeSpecificAccentColors: [Int64: PresentationThemeAccentColor]
public var themeSpecificCustomColors: [Int64: PresentationThemeCustomColors]
public var themeSpecificChatWallpapers: [Int64: TelegramWallpaper]
public var useSystemFont: Bool
public var fontSize: PresentationFontSize
@ -471,16 +517,24 @@ public struct PresentationThemeSettings: PreferencesEntry {
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
}
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.themeSpecificAccentColors = themeSpecificAccentColors
self.themeSpecificCustomColors = themeSpecificCustomColors
self.themeSpecificChatWallpapers = themeSpecificChatWallpapers
self.useSystemFont = useSystemFont
self.fontSize = fontSize
@ -498,6 +552,12 @@ public struct PresentationThemeSettings: PreferencesEntry {
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
return decoder.decodeInt64ForKey("k", orElse: 0)
}, valueDecoder: { decoder in
@ -516,6 +576,9 @@ public struct PresentationThemeSettings: PreferencesEntry {
encoder.encodeObjectDictionary(self.themeSpecificAccentColors, forKey: "themeSpecificAccentColors", keyEncoder: { key, encoder in
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.encodeInt64(key, forKey: "k")
})
@ -535,7 +598,43 @@ public struct PresentationThemeSettings: PreferencesEntry {
}
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> {
let colorsSignal: Signal<((UIColor, UIColor?), (UIColor, UIColor), (UIColor, UIColor), UIImage?), 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?, Int32?), NoError>
if case let .builtin(theme) = theme {
let backgroundColor: UIColor
let incomingColor: UIColor
let outgoingColor: (UIColor, UIColor)
var accentColor = accentColor
var accentColor = color?.color
var bubbleColors = color?.plainBubbleColors
var topBackgroundColor: UIColor
var bottomBackgroundColor: UIColor?
switch theme {
case .dayClassic:
incomingColor = UIColor(rgb: 0xffffff)
if let accentColor = accentColor {
if let bubbleColors = bubbleColors {
backgroundColor = UIColor(rgb: 0xd6e2ee)
topBackgroundColor = UIColor(rgb: 0xd6e2ee)
outgoingColor = bubbleColors
} 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 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)
}
} else {
backgroundColor = UIColor(rgb: 0xd6e2ee)
topBackgroundColor = UIColor(rgb: 0xd6e2ee)
outgoingColor = (UIColor(rgb: 0xe1ffc7), UIColor(rgb: 0xe1ffc7))
}
case .day:
backgroundColor = UIColor(rgb: 0xffffff)
topBackgroundColor = UIColor(rgb: 0xffffff)
incomingColor = UIColor(rgb: 0xd5dde6)
if accentColor == nil {
accentColor = UIColor(rgb: 0x007aff)
}
outgoingColor = bubbleColors ?? (accentColor!, accentColor!)
case .night:
backgroundColor = UIColor(rgb: 0x000000)
topBackgroundColor = UIColor(rgb: 0x000000)
incomingColor = UIColor(rgb: 0x1f1f1f)
if accentColor == nil {
accentColor = UIColor(rgb: 0x313131)
@ -1096,12 +1098,33 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
outgoingColor = bubbleColors ?? (accentColor!, accentColor!)
case .nightAccent:
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)
let accentBubbleColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59)
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 {
var resource: MediaResource?
if case let .local(theme) = theme {
@ -1111,9 +1134,10 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
}
if let resource = resource {
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) {
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 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)
@ -1122,11 +1146,13 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
backgroundColor = (UIColor(rgb: 0xd6e2ee), nil)
case let .color(color):
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)))
rotation = settings.rotation
case .image:
backgroundColor = (.black, nil)
case let .file(file):
rotation = file.settings.rotation
if file.isPattern, let color = file.settings.color {
backgroundColor = (UIColor(rgb: UInt32(bitPattern: color)), file.settings.bottomColor.flatMap { UIColor(rgb: UInt32(bitPattern: $0)) })
} else {
@ -1138,7 +1164,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
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)))
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 {
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)
|> mapToSignal { _ in
if let image = UIImage(data: fullSizeData) {
return .single((backgroundColor, incomingColor, outgoingColor, image))
return .single((backgroundColor, incomingColor, outgoingColor, image, rotation))
} else {
return .complete()
}
}
} else if let image = UIImage(data: fullSizeData) {
return .single((backgroundColor, incomingColor, outgoingColor, image))
return .single((backgroundColor, incomingColor, outgoingColor, image, rotation))
} else {
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)
} else {
return .complete()
@ -1192,7 +1218,14 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 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 {
c.setFillColor(colors.0.0.cgColor)
c.fill(drawingRect)