Swiftgram/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift
2021-09-05 14:36:45 +03:00

1467 lines
84 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import AlertUI
import PresentationDataUtils
import MediaResources
import WallpaperResources
import ShareController
import AccountContext
import ContextUI
import UndoUI
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController
weak var sourceNode: ASDisplayNode?
let navigationController: NavigationController? = nil
let passthroughTouches: Bool = false
init(controller: ViewController, sourceNode: ASDisplayNode?) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerTakeControllerInfo? {
let sourceNode = self.sourceNode
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
if let sourceNode = sourceNode {
return (sourceNode, sourceNode.bounds)
} else {
return nil
}
})
}
func animatedIn() {
}
}
func themeDisplayName(strings: PresentationStrings, reference: PresentationThemeReference) -> String {
let name: String
switch reference {
case let .builtin(theme):
switch theme {
case .dayClassic:
name = strings.Appearance_ThemeCarouselClassic
case .day:
name = strings.Appearance_ThemeCarouselDay
case .night:
name = strings.Appearance_ThemeCarouselNewNight
case .nightAccent:
name = strings.Appearance_ThemeCarouselTintedNight
}
case let .local(theme):
name = theme.title
case let .cloud(theme):
name = theme.theme.title
}
return name
}
private final class ThemeSettingsControllerArguments {
let context: AccountContext
let selectTheme: (PresentationThemeReference) -> Void
let selectFontSize: (PresentationFontSize) -> Void
let openWallpaperSettings: () -> Void
let selectAccentColor: (PresentationThemeAccentColor?) -> Void
let openAccentColorPicker: (PresentationThemeReference, Bool) -> Void
let openAutoNightTheme: () -> Void
let openTextSize: () -> Void
let openBubbleSettings: () -> Void
let toggleLargeEmoji: (Bool) -> Void
let disableAnimations: (Bool) -> Void
let selectAppIcon: (String) -> Void
let editTheme: (PresentationCloudTheme) -> Void
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, 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, openBubbleSettings: @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 (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context
self.selectTheme = selectTheme
self.selectFontSize = selectFontSize
self.openWallpaperSettings = openWallpaperSettings
self.selectAccentColor = selectAccentColor
self.openAccentColorPicker = openAccentColorPicker
self.openAutoNightTheme = openAutoNightTheme
self.openTextSize = openTextSize
self.openBubbleSettings = openBubbleSettings
self.toggleLargeEmoji = toggleLargeEmoji
self.disableAnimations = disableAnimations
self.selectAppIcon = selectAppIcon
self.editTheme = editTheme
self.themeContextAction = themeContextAction
self.colorContextAction = colorContextAction
}
}
private enum ThemeSettingsControllerSection: Int32 {
case chatPreview
case background
case fontSize
case icon
case other
}
public enum ThemeSettingsEntryTag: ItemListItemTag {
case fontSize
case theme
case tint
case accentColor
case icon
case largeEmoji
case animations
public func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? ThemeSettingsEntryTag, self == other {
return true
} else {
return false
}
}
}
private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case themeListHeader(PresentationTheme, String)
case fontSizeHeader(PresentationTheme, String)
case fontSize(PresentationTheme, PresentationFontSize)
case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeReference, [PresentationThemeReference], ThemeSettingsColorOption?)
case autoNightTheme(PresentationTheme, String, String)
case textSize(PresentationTheme, String, String)
case bubbleSettings(PresentationTheme, String, String)
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?)
case iconHeader(PresentationTheme, String)
case iconItem(PresentationTheme, PresentationStrings, [PresentationAppIcon], String?)
case otherHeader(PresentationTheme, String)
case largeEmoji(PresentationTheme, String, Bool)
case animations(PresentationTheme, String, Bool)
case animationsInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
case .themeListHeader, .chatPreview, .themeItem, .accentColor:
return ThemeSettingsControllerSection.chatPreview.rawValue
case .fontSizeHeader, .fontSize:
return ThemeSettingsControllerSection.fontSize.rawValue
case .wallpaper, .autoNightTheme, .textSize, .bubbleSettings:
return ThemeSettingsControllerSection.background.rawValue
case .iconHeader, .iconItem:
return ThemeSettingsControllerSection.icon.rawValue
case .otherHeader, .largeEmoji, .animations, .animationsInfo:
return ThemeSettingsControllerSection.other.rawValue
}
}
var stableId: Int32 {
switch self {
case .themeListHeader:
return 0
case .chatPreview:
return 1
case .themeItem:
return 2
case .accentColor:
return 4
case .wallpaper:
return 5
case .autoNightTheme:
return 6
case .textSize:
return 7
case .bubbleSettings:
return 8
case .fontSizeHeader:
return 9
case .fontSize:
return 10
case .iconHeader:
return 11
case .iconItem:
return 12
case .otherHeader:
return 13
case .largeEmoji:
return 14
case .animations:
return 15
case .animationsInfo:
return 16
}
}
static func ==(lhs: ThemeSettingsControllerEntry, rhs: ThemeSettingsControllerEntry) -> Bool {
switch lhs {
case let .chatPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsChatBubbleCorners, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsItems):
if case let .chatPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsChatBubbleCorners, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsItems) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsChatBubbleCorners == rhsChatBubbleCorners, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsItems == rhsItems {
return true
} else {
return false
}
case let .wallpaper(lhsTheme, lhsText):
if case let .wallpaper(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .accentColor(lhsTheme, _, lhsCurrentTheme, lhsThemes, lhsColor):
if case let .accentColor(rhsTheme, _, rhsCurrentTheme, rhsThemes, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsThemes == rhsThemes, lhsColor == rhsColor {
return true
} else {
return false
}
case let .autoNightTheme(lhsTheme, lhsText, lhsValue):
if case let .autoNightTheme(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .textSize(lhsTheme, lhsText, lhsValue):
if case let .textSize(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .bubbleSettings(lhsTheme, lhsText, lhsValue):
if case let .bubbleSettings(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .themeListHeader(lhsTheme, lhsText):
if case let .themeListHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsAllThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsThemeSpecificChatWallpapers, lhsCurrentColor):
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsAllThemes, rhsCurrentTheme, rhsThemeAccentColors, rhsThemeSpecificChatWallpapers, rhsCurrentColor) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsAllThemes == rhsAllThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors, lhsThemeSpecificChatWallpapers == rhsThemeSpecificChatWallpapers, lhsCurrentColor == rhsCurrentColor {
return true
} else {
return false
}
case let .fontSizeHeader(lhsTheme, lhsText):
if case let .fontSizeHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .fontSize(lhsTheme, lhsFontSize):
if case let .fontSize(rhsTheme, rhsFontSize) = rhs, lhsTheme === rhsTheme, lhsFontSize == rhsFontSize {
return true
} else {
return false
}
case let .iconHeader(lhsTheme, lhsText):
if case let .iconHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .iconItem(lhsTheme, lhsStrings, lhsIcons, lhsValue):
if case let .iconItem(rhsTheme, rhsStrings, rhsIcons, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsIcons == rhsIcons, lhsValue == rhsValue {
return true
} else {
return false
}
case let .otherHeader(lhsTheme, lhsText):
if case let .otherHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .largeEmoji(lhsTheme, lhsTitle, lhsValue):
if case let .largeEmoji(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .animations(lhsTheme, lhsTitle, lhsValue):
if case let .animations(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .animationsInfo(lhsTheme, lhsText):
if case let .animationsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
}
}
static func <(lhs: ThemeSettingsControllerEntry, rhs: ThemeSettingsControllerEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ThemeSettingsControllerArguments
switch self {
case let .fontSizeHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .fontSize(theme, fontSize):
return ThemeSettingsFontSizeItem(theme: theme, fontSize: fontSize, sectionId: self.section, updated: { value in
arguments.selectFontSize(value)
}, tag: ThemeSettingsEntryTag.fontSize)
case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items):
return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items)
case let .wallpaper(_, text):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openWallpaperSettings()
})
case let .accentColor(theme, generalThemeReference, currentTheme, themes, color):
var colorItems: [ThemeSettingsAccentColor] = []
for theme in themes {
colorItems.append(.theme(theme))
}
var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue)
var colors = PresentationThemeBaseColor.allCases
colors = colors.filter { $0 != .custom && $0 != .preset && $0 != .theme }
if case let .builtin(name) = generalThemeReference {
if name == .dayClassic {
colorItems.append(.default)
defaultColor = nil
for preset in dayClassicColorPresets {
colorItems.append(.preset(preset))
}
} else if name == .day {
colorItems.append(.color(.blue))
colors = colors.filter { $0 != .blue }
for preset in dayColorPresets {
colorItems.append(.preset(preset))
}
} else if name == .night {
colorItems.append(.color(.blue))
colors = colors.filter { $0 != .blue }
for preset in nightColorPresets {
colorItems.append(.preset(preset))
}
}
if name != .day {
colors = colors.filter { $0 != .black }
}
if name == .night {
colors = colors.filter { $0 != .gray }
} else {
colors = colors.filter { $0 != .white }
}
}
var currentColor = color ?? defaultColor.flatMap { .accentColor($0) }
if let color = currentColor, case let .accentColor(accentColor) = color, accentColor.baseColor == .theme {
var themeExists = false
if let _ = themes.first(where: { $0.index == accentColor.themeIndex }) {
themeExists = true
}
if !themeExists {
currentColor = defaultColor.flatMap { .accentColor($0) }
}
}
colorItems.append(contentsOf: colors.map { .color($0) })
if let index = colorItems.firstIndex(where: { item in
if case .default = item {
return true
} else {
return false
}
}) {
if index > 0 {
let item = colorItems[index]
colorItems.remove(at: index)
colorItems.insert(item, at: 1)
}
}
return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, generalThemeReference: generalThemeReference, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in
if let color = color {
switch color {
case let .accentColor(color):
arguments.selectAccentColor(color)
case let .theme(theme):
arguments.selectTheme(theme)
}
} else {
arguments.selectAccentColor(nil)
}
}, contextAction: { isCurrent, theme, color, node, gesture in
arguments.colorContextAction(isCurrent, theme, color, node, gesture)
}, openColorPicker: { create in
arguments.openAccentColorPicker(currentTheme, create)
}, tag: ThemeSettingsEntryTag.accentColor)
case let .autoNightTheme(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openAutoNightTheme()
})
case let .textSize(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openTextSize()
})
case let .bubbleSettings(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openBubbleSettings()
})
case let .themeListHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _):
return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, allThemes: allThemes, displayUnsupported: true, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, currentTheme: currentTheme, updatedTheme: { theme in
if case let .cloud(theme) = theme, theme.theme.file == nil && theme.theme.settings == nil {
if theme.theme.isCreator {
arguments.editTheme(theme)
}
} else {
arguments.selectTheme(theme)
}
}, contextAction: { theme, node, gesture in
arguments.themeContextAction(theme.index == currentTheme.index, theme, node, gesture)
}, tag: ThemeSettingsEntryTag.theme)
case let .iconHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .iconItem(theme, strings, icons, value):
return ThemeSettingsAppIconItem(theme: theme, strings: strings, sectionId: self.section, icons: icons, currentIconName: value, updated: { iconName in
arguments.selectAppIcon(iconName)
})
case let .otherHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .largeEmoji(_, title, value):
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleLargeEmoji(value)
}, tag: ThemeSettingsEntryTag.largeEmoji)
case let .animations(_, title, value):
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.disableAnimations(value)
}, tag: ThemeSettingsEntryTag.animations)
case let .animationsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
}
}
}
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?) -> [ThemeSettingsControllerEntry] {
var entries: [ThemeSettingsControllerEntry] = []
let strings = presentationData.strings
let title = presentationData.autoNightModeTriggered ? strings.Appearance_ColorThemeNight.uppercased() : strings.Appearance_ColorTheme.uppercased()
entries.append(.themeListHeader(presentationData.theme, title))
entries.append(.chatPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.chatFontSize, presentationData.chatBubbleCorners, 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)]))
let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
if case let .cloud(theme) = reference {
return theme.theme.settings == nil
} else {
return true
}
}
let generalThemeReference: PresentationThemeReference
if case let .cloud(theme) = themeReference, let settings = theme.theme.settings {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = themeReference
}
entries.append(.themeItem(presentationData.theme, presentationData.strings, generalThemes, availableThemes, themeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificChatWallpapers, presentationThemeSettings.themeSpecificAccentColors[themeReference.index]))
if case let .builtin(builtinTheme) = generalThemeReference {
let colorThemes = availableThemes.filter { reference in
if case let .cloud(theme) = reference, let settings = theme.theme.settings, settings.baseTheme == builtinTheme.baseTheme {
return true
} else {
return false
}
}
var colorOption: ThemeSettingsColorOption?
if case .builtin = themeReference {
colorOption = presentationThemeSettings.themeSpecificAccentColors[themeReference.index].flatMap { .accentColor($0) }
} else {
colorOption = .theme(themeReference)
}
entries.append(.accentColor(presentationData.theme, generalThemeReference, themeReference, colorThemes, colorOption))
}
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
let autoNightMode: String
switch presentationThemeSettings.automaticThemeSwitchSetting.trigger {
case .system:
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
autoNightMode = strings.AutoNightTheme_System
} else {
autoNightMode = strings.AutoNightTheme_Disabled
}
case .explicitNone:
autoNightMode = strings.AutoNightTheme_Disabled
case .timeBased:
autoNightMode = strings.AutoNightTheme_Scheduled
case .brightness:
autoNightMode = strings.AutoNightTheme_Automatic
}
entries.append(.autoNightTheme(presentationData.theme, strings.Appearance_AutoNightTheme, autoNightMode))
let textSizeValue: String
if presentationThemeSettings.useSystemFont {
textSizeValue = strings.Appearance_TextSize_Automatic
} else {
if presentationThemeSettings.fontSize.baseDisplaySize == presentationThemeSettings.listsFontSize.baseDisplaySize {
textSizeValue = "\(Int(presentationThemeSettings.fontSize.baseDisplaySize))pt"
} else {
textSizeValue = "\(Int(presentationThemeSettings.fontSize.baseDisplaySize))pt / \(Int(presentationThemeSettings.listsFontSize.baseDisplaySize))pt"
}
}
entries.append(.textSize(presentationData.theme, strings.Appearance_TextSizeSetting, textSizeValue))
entries.append(.bubbleSettings(presentationData.theme, strings.Appearance_BubbleCornersSetting, ""))
if !availableAppIcons.isEmpty {
entries.append(.iconHeader(presentationData.theme, strings.Appearance_AppIcon.uppercased()))
entries.append(.iconItem(presentationData.theme, presentationData.strings, availableAppIcons, currentAppIconName))
}
entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased()))
entries.append(.largeEmoji(presentationData.theme, strings.Appearance_LargeEmoji, presentationData.largeEmoji))
entries.append(.animations(presentationData.theme, strings.Appearance_ReduceMotion, presentationData.reduceMotion))
entries.append(.animationsInfo(presentationData.theme, strings.Appearance_ReduceMotionInfo))
return entries
}
public protocol ThemeSettingsController {
}
private final class ThemeSettingsControllerImpl: ItemListController, ThemeSettingsController {
}
public func themeSettingsController(context: AccountContext, focusOnItemTag: ThemeSettingsEntryTag? = nil) -> ViewController {
#if DEBUG
BuiltinWallpaperData.generate(account: context.account)
#endif
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var updateControllersImpl: ((([UIViewController]) -> [UIViewController]) -> Void)?
var presentInGlobalOverlayImpl: ((ViewController, Any?) -> Void)?
var getNavigationControllerImpl: (() -> NavigationController?)?
var presentCrossfadeControllerImpl: ((Bool) -> Void)?
var selectThemeImpl: ((PresentationThemeReference) -> Void)?
var selectAccentColorImpl: ((PresentationThemeAccentColor?) -> Void)?
var openAccentColorPickerImpl: ((PresentationThemeReference, Bool) -> Void)?
var moreImpl: (() -> Void)?
let _ = telegramWallpapers(postbox: context.account.postbox, network: context.account.network).start()
let currentAppIcon: PresentationAppIcon?
let appIcons = context.sharedContext.applicationBindings.getAvailableAlternateIcons()
if let alternateIconName = context.sharedContext.applicationBindings.getAlternateIconName() {
currentAppIcon = appIcons.filter { $0.name == alternateIconName }.first
} else {
currentAppIcon = appIcons.filter { $0.isDefault }.first
}
let availableAppIcons: Signal<[PresentationAppIcon], NoError> = .single(appIcons)
let currentAppIconName = ValuePromise<String?>()
currentAppIconName.set(currentAppIcon?.name ?? "Blue")
let cloudThemes = Promise<[TelegramTheme]>()
let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager)
cloudThemes.set(updatedCloudThemes)
let removedThemeIndexesPromise = Promise<Set<Int64>>(Set())
let removedThemeIndexes = Atomic<Set<Int64>>(value: Set())
let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in
selectThemeImpl?(theme)
}, selectFontSize: { _ in
}, openWallpaperSettings: {
pushControllerImpl?(ThemeGridController(context: context))
}, selectAccentColor: { accentColor in
selectAccentColorImpl?(accentColor)
}, openAccentColorPicker: { themeReference, create in
openAccentColorPickerImpl?(themeReference, create)
}, openAutoNightTheme: {
pushControllerImpl?(themeAutoNightSettingsController(context: context))
}, openTextSize: {
let _ = (context.sharedContext.accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.presentationThemeSettings]))
|> take(1)
|> deliverOnMainQueue).start(next: { view in
let settings = (view.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
pushControllerImpl?(TextSizeSelectionController(context: context, presentationThemeSettings: settings))
})
}, openBubbleSettings: {
let _ = (context.sharedContext.accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.presentationThemeSettings]))
|> take(1)
|> deliverOnMainQueue).start(next: { view in
let settings = (view.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
pushControllerImpl?(BubbleSettingsController(context: context, presentationThemeSettings: settings))
})
}, toggleLargeEmoji: { largeEmoji in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedLargeEmoji(largeEmoji)
}).start()
}, disableAnimations: { reduceMotion in
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedReduceMotion(reduceMotion)
}).start()
}, selectAppIcon: { name in
currentAppIconName.set(name)
context.sharedContext.applicationBindings.requestSetAlternateIconName(name, { _ in
})
}, editTheme: { theme in
let controller = editThemeController(context: context, mode: .edit(theme), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
pushControllerImpl?(controller)
}, 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
let accentColor = settings.themeSpecificAccentColors[reference.index]
var wallpaper: TelegramWallpaper?
if let accentColor = accentColor {
wallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: reference, accentColor: accentColor)]
}
if wallpaper == nil {
wallpaper = settings.themeSpecificChatWallpapers[reference.index]
}
return (accentColor, wallpaper)
}
|> map { accentColor, wallpaper -> (PresentationThemeAccentColor?, TelegramWallpaper) in
let effectiveWallpaper: TelegramWallpaper
if let wallpaper = wallpaper {
effectiveWallpaper = wallpaper
} else {
let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors ?? [], wallpaper: accentColor?.wallpaper)
effectiveWallpaper = theme?.chat.defaultWallpaper ?? .builtin(WallpaperSettings())
}
return (accentColor, effectiveWallpaper)
}
|> mapToSignal { accentColor, wallpaper -> Signal<(PresentationThemeAccentColor?, TelegramWallpaper), NoError> in
if case let .file(file) = wallpaper, file.id == 0 {
return cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings)
|> map { cachedWallpaper in
if let wallpaper = cachedWallpaper?.wallpaper, case .file = wallpaper {
return (accentColor, wallpaper)
} else {
return (accentColor, .builtin(WallpaperSettings()))
}
}
} else {
return .single((accentColor, wallpaper))
}
}
|> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, TelegramWallpaper?), NoError> in
return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.sharedContext.accountManager.mediaBox)
|> map { serviceBackgroundColor in
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors ?? [], serviceBackgroundColor: serviceBackgroundColor), wallpaper)
}
}
|> deliverOnMainQueue).start(next: { theme, wallpaper in
guard let theme = theme else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, wallpaper))
var items: [ContextMenuItem] = []
if case let .cloud(theme) = reference {
if theme.theme.isCreator {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_EditTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) }, action: { c, f in
let controller = editThemeController(context: context, mode: .edit(theme), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
c.dismiss(completion: {
pushControllerImpl?(controller)
})
})))
} else {
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
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, preview: false) else {
return
}
let resolvedWallpaper: Signal<TelegramWallpaper, NoError>
if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings)
|> map { cachedWallpaper -> TelegramWallpaper in
return cachedWallpaper?.wallpaper ?? theme.chat.defaultWallpaper
}
} else {
resolvedWallpaper = .single(theme.chat.defaultWallpaper)
}
let _ = (resolvedWallpaper
|> deliverOnMainQueue).start(next: { wallpaper in
let controller = ThemeAccentColorController(context: context, mode: .edit(settings: nil, theme: theme, wallpaper: wallpaper, generalThemeReference: reference.generalThemeReference, defaultThemeReference: nil, create: true, completion: { result, settings in
let controller = editThemeController(context: context, mode: .create(result, settings
), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
updateControllersImpl?({ controllers in
var controllers = controllers
controllers = controllers.filter { controller in
if controller is ThemeAccentColorController {
return false
}
return true
}
controllers.append(controller)
return controllers
})
}))
c.dismiss(completion: {
pushControllerImpl?(controller)
})
})
})))
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: {
let shareController = ShareController(context: context, subject: .url("https://t.me/addtheme/\(theme.theme.slug)"), preferredAction: .default)
shareController.actionCompleted = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
presentControllerImpl?(shareController, nil)
})
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveTheme, 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 actionSheet = ActionSheetController(presentationData: presentationData)
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeConfirmation, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = (cloudThemes.get()
|> take(1)
|> deliverOnMainQueue).start(next: { themes in
removedThemeIndexesPromise.set(.single(removedThemeIndexes.modify({ value in
var updated = value
updated.insert(theme.theme.id)
return updated
})))
if isCurrent, let currentThemeIndex = themes.firstIndex(where: { $0.id == theme.theme.id }) {
if let settings = theme.theme.settings {
if settings.baseTheme == .night {
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
} else {
selectAccentColorImpl?(nil)
}
} else {
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
let newTheme: PresentationThemeReference
if let previousThemeIndex = previousThemeIndex {
let theme = themes[themes.index(before: previousThemeIndex.base)]
newTheme = .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil))
} else {
newTheme = .builtin(.nightAccent)
}
selectThemeImpl?(newTheme)
}
}
let _ = deleteThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme.theme).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)
})
})))
} else {
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, create: true))
pushControllerImpl?(controller)
})
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
}, colorContextAction: { isCurrent, reference, accentColor, node, gesture in
let _ = (context.sharedContext.accountManager.transaction { transaction -> (ThemeSettingsColorOption?, TelegramWallpaper?) in
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
var wallpaper: TelegramWallpaper?
if let accentColor = accentColor {
switch accentColor {
case let .accentColor(accentColor):
wallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: reference, accentColor: accentColor)]
if wallpaper == nil {
wallpaper = settings.themeSpecificChatWallpapers[reference.index]
}
case let .theme(theme):
wallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: theme, accentColor: nil)]
}
} else if wallpaper == nil {
wallpaper = settings.themeSpecificChatWallpapers[reference.index]
}
return (accentColor, wallpaper)
} |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, PresentationThemeReference, Bool, TelegramWallpaper?), NoError> in
let generalThemeReference: PresentationThemeReference
if let _ = accentColor, case let .cloud(theme) = reference, let settings = theme.theme.settings {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = reference
}
let effectiveWallpaper: TelegramWallpaper
let effectiveThemeReference: PresentationThemeReference
if let accentColor = accentColor, case let .theme(themeReference) = accentColor {
effectiveThemeReference = themeReference
} else {
effectiveThemeReference = reference
}
if let wallpaper = wallpaper {
effectiveWallpaper = wallpaper
} else {
let theme: PresentationTheme?
if let accentColor = accentColor, case let .theme(themeReference) = accentColor {
theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference)
} else {
var baseColor: PresentationThemeBaseColor?
switch accentColor {
case let .accentColor(value):
baseColor = value.baseColor
default:
break
}
theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors ?? [], wallpaper: accentColor?.wallpaper, baseColor: baseColor)
}
effectiveWallpaper = theme?.chat.defaultWallpaper ?? .builtin(WallpaperSettings())
}
let wallpaperSignal: Signal<TelegramWallpaper, NoError>
if case let .file(file) = effectiveWallpaper, file.id == 0 {
wallpaperSignal = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings)
|> map { cachedWallpaper in
return cachedWallpaper?.wallpaper ?? effectiveWallpaper
}
} else {
wallpaperSignal = .single(effectiveWallpaper)
}
return wallpaperSignal
|> mapToSignal { wallpaper in
return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.sharedContext.accountManager.mediaBox)
|> map { serviceBackgroundColor in
return (wallpaper, serviceBackgroundColor)
}
}
|> map { wallpaper, serviceBackgroundColor -> (PresentationTheme?, PresentationThemeReference, TelegramWallpaper) in
if let accentColor = accentColor, case let .theme(themeReference) = accentColor {
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, serviceBackgroundColor: serviceBackgroundColor), effectiveThemeReference, wallpaper)
} else {
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors ?? [], serviceBackgroundColor: serviceBackgroundColor), effectiveThemeReference, wallpaper)
}
}
|> mapToSignal { theme, reference, wallpaper in
if case let .cloud(info) = reference {
return cloudThemes.get()
|> take(1)
|> map { themes -> Bool in
if let _ = themes.first(where: { $0.id == info.theme.id }) {
return true
} else {
return false
}
}
|> map { cloudThemeExists -> (PresentationTheme?, PresentationThemeReference, Bool, TelegramWallpaper) in
return (theme, reference, cloudThemeExists, wallpaper)
}
} else {
return .single((theme, reference, false, wallpaper))
}
}
}
|> deliverOnMainQueue).start(next: { theme, effectiveThemeReference, cloudThemeExists, wallpaper in
guard let theme = theme else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(effectiveThemeReference, wallpaper))
var items: [ContextMenuItem] = []
if let accentColor = accentColor {
if case let .accentColor(color) = accentColor, color.baseColor != .custom {
} else if case let .theme(theme) = accentColor, case let .cloud(cloudTheme) = theme {
if cloudTheme.theme.isCreator && cloudThemeExists {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_EditTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) }, action: { c, f in
let controller = editThemeController(context: context, mode: .edit(cloudTheme), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
c.dismiss(completion: {
pushControllerImpl?(controller)
})
})))
} else {
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
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: effectiveThemeReference, preview: false) else {
return
}
let resolvedWallpaper: Signal<TelegramWallpaper, NoError>
if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings)
|> map { cachedWallpaper -> TelegramWallpaper in
return cachedWallpaper?.wallpaper ?? theme.chat.defaultWallpaper
}
} else {
resolvedWallpaper = .single(theme.chat.defaultWallpaper)
}
let _ = (resolvedWallpaper
|> deliverOnMainQueue).start(next: { wallpaper in
var hasSettings = false
var settings: TelegramThemeSettings?
if case let .cloud(cloudTheme) = effectiveThemeReference, cloudTheme.theme.settings != nil {
hasSettings = true
settings = cloudTheme.theme.settings
}
let controller = ThemeAccentColorController(context: context, mode: .edit(settings: settings, theme: theme, wallpaper: wallpaper, generalThemeReference: effectiveThemeReference.generalThemeReference, defaultThemeReference: nil, create: true, completion: { result, settings in
let controller = editThemeController(context: context, mode: .create(hasSettings ? nil : result, hasSettings ? settings : nil), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
updateControllersImpl?({ controllers in
var controllers = controllers
controllers = controllers.filter { controller in
if controller is ThemeAccentColorController {
return false
}
return true
}
controllers.append(controller)
return controllers
})
}))
c.dismiss(completion: {
pushControllerImpl?(controller)
})
})
})))
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: {
let shareController = ShareController(context: context, subject: .url("https://t.me/addtheme/\(cloudTheme.theme.slug)"), preferredAction: .default)
shareController.actionCompleted = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
presentControllerImpl?(shareController, nil)
})
})))
if cloudThemeExists {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveTheme, 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 actionSheet = ActionSheetController(presentationData: presentationData)
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeConfirmation, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = (cloudThemes.get()
|> take(1)
|> deliverOnMainQueue).start(next: { themes in
removedThemeIndexesPromise.set(.single(removedThemeIndexes.modify({ value in
var updated = value
updated.insert(cloudTheme.theme.id)
return updated
})))
if isCurrent, let settings = cloudTheme.theme.settings {
let colorThemes = themes.filter { theme in
if let _ = theme.settings {
return true
} else {
return false
}
}
if let currentThemeIndex = colorThemes.firstIndex(where: { $0.id == cloudTheme.theme.id }) {
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
if let previousThemeIndex = previousThemeIndex {
let theme = themes[themes.index(before: previousThemeIndex.base)]
selectThemeImpl?(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? context.account.id : nil)))
} else {
if settings.baseTheme == .night {
selectAccentColorImpl?(PresentationThemeAccentColor(baseColor: .blue))
} else {
selectAccentColorImpl?(nil)
}
}
}
}
let _ = deleteThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: cloudTheme.theme).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)
})
})))
}
}
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
})
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get())
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes -> (ItemListControllerState, (ItemListNodeState, Any)) in
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
let themeReference: PresentationThemeReference
if presentationData.autoNightModeTriggered {
themeReference = settings.automaticThemeSwitchSetting.theme
} else {
themeReference = settings.theme
}
let rightNavigationButton = ItemListNavigationButton(content: .icon(.add), style: .regular, enabled: true, action: {
moreImpl?()
})
var defaultThemes: [PresentationThemeReference] = []
if presentationData.autoNightModeTriggered {
defaultThemes.append(contentsOf: [.builtin(.nightAccent), .builtin(.night)])
} else {
defaultThemes.append(contentsOf: [
.builtin(.dayClassic),
.builtin(.nightAccent),
.builtin(.day),
.builtin(.night)
])
}
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? context.account.id : nil)) }.filter { !removedThemeIndexes.contains($0.index) }
var availableThemes = defaultThemes
if defaultThemes.first(where: { $0.index == themeReference.index }) == nil && cloudThemes.first(where: { $0.index == themeReference.index }) == nil {
availableThemes.append(themeReference)
}
availableThemes.append(contentsOf: cloudThemes)
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
return (controllerState, (listState, arguments))
}
let controller = ThemeSettingsControllerImpl(context: context, state: signal)
controller.alwaysSynchronous = true
pushControllerImpl = { [weak controller] c in
(controller?.navigationController as? NavigationController)?.pushViewController(c)
}
presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a, blockInteraction: true)
}
updateControllersImpl = { [weak controller] f in
if let navigationController = controller?.navigationController as? NavigationController {
navigationController.setViewControllers(f(navigationController.viewControllers), animated: true)
}
}
presentInGlobalOverlayImpl = { [weak controller] c, a in
controller?.presentInGlobalOverlay(c, with: a)
}
getNavigationControllerImpl = { [weak controller] in
return controller?.navigationController as? NavigationController
}
presentCrossfadeControllerImpl = { [weak controller] hasAccentColors in
if let controller = controller, controller.isNodeLoaded, let navigationController = controller.navigationController as? NavigationController, navigationController.topViewController === controller {
var topOffset: CGFloat?
var bottomOffset: CGFloat?
var leftOffset: CGFloat?
var themeItemNode: ThemeSettingsThemeItemNode?
var colorItemNode: ThemeSettingsAccentColorItemNode?
var view: UIView?
if #available(iOS 11.0, *) {
view = controller.navigationController?.view
}
let controllerFrame = controller.view.convert(controller.view.bounds, to: controller.navigationController?.view)
if controllerFrame.minX > 0.0 {
leftOffset = controllerFrame.minX
}
if controllerFrame.minY > 100.0 {
view = nil
}
controller.forEachItemNode { node in
if let itemNode = node as? ItemListItemNode {
if let itemTag = itemNode.tag {
if itemTag.isEqual(to: ThemeSettingsEntryTag.theme) {
let frame = node.view.convert(node.view.bounds, to: controller.navigationController?.view)
topOffset = frame.minY
bottomOffset = frame.maxY
if let itemNode = node as? ThemeSettingsThemeItemNode {
themeItemNode = itemNode
}
} else if itemTag.isEqual(to: ThemeSettingsEntryTag.accentColor) && hasAccentColors {
let frame = node.view.convert(node.view.bounds, to: controller.navigationController?.view)
bottomOffset = frame.maxY
if let itemNode = node as? ThemeSettingsAccentColorItemNode {
colorItemNode = itemNode
}
}
}
}
}
if let navigationBar = controller.navigationBar {
if let offset = topOffset {
topOffset = max(offset, navigationBar.frame.maxY)
} else {
topOffset = navigationBar.frame.maxY
}
}
if view != nil {
themeItemNode?.prepareCrossfadeTransition()
colorItemNode?.prepareCrossfadeTransition()
}
let crossfadeController = ThemeSettingsCrossfadeController(view: view, topOffset: topOffset, bottomOffset: bottomOffset, leftOffset: leftOffset)
crossfadeController.didAppear = { [weak themeItemNode, weak colorItemNode] in
if view != nil {
themeItemNode?.animateCrossfadeTransition()
colorItemNode?.animateCrossfadeTransition()
}
}
context.sharedContext.presentGlobalController(crossfadeController, nil)
}
}
selectThemeImpl = { theme in
guard let presentationTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: theme) else {
return
}
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered
let resolvedWallpaper: Signal<TelegramWallpaper?, NoError>
if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 {
resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings)
|> map { wallpaper -> TelegramWallpaper? in
return wallpaper?.wallpaper
}
} else {
resolvedWallpaper = .single(nil)
}
var cloudTheme: TelegramTheme?
if case let .cloud(theme) = theme {
cloudTheme = theme.theme
}
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: cloudTheme).start()
let currentTheme = context.sharedContext.accountManager.transaction { transaction -> (PresentationThemeReference) in
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
if autoNightModeTriggered {
return settings.automaticThemeSwitchSetting.theme
} else {
return settings.theme
}
}
let _ = (combineLatest(resolvedWallpaper, currentTheme)
|> map { resolvedWallpaper, currentTheme -> Bool in
var updatedTheme = theme
var currentThemeBaseIndex: Int64?
if case let .cloud(info) = currentTheme, let settings = info.theme.settings {
currentThemeBaseIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
} else {
currentThemeBaseIndex = currentTheme.index
}
var baseThemeIndex: Int64?
var updatedThemeBaseIndex: Int64?
if case let .cloud(info) = theme {
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
if let settings = info.theme.settings {
baseThemeIndex = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)).index
updatedThemeBaseIndex = baseThemeIndex
}
} else {
updatedThemeBaseIndex = theme.index
}
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var updatedThemeSpecificAccentColors = current.themeSpecificAccentColors
if let baseThemeIndex = baseThemeIndex {
updatedThemeSpecificAccentColors[baseThemeIndex] = PresentationThemeAccentColor(themeIndex: updatedTheme.index)
}
if autoNightModeTriggered {
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
updatedAutomaticThemeSwitchSetting.theme = updatedTheme
return current.withUpdatedAutomaticThemeSwitchSetting(updatedAutomaticThemeSwitchSetting).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
} else {
return current.withUpdatedTheme(updatedTheme).withUpdatedThemeSpecificAccentColors(updatedThemeSpecificAccentColors)
}
}).start()
return currentThemeBaseIndex != updatedThemeBaseIndex
} |> deliverOnMainQueue).start(next: { crossfadeAccentColors in
presentCrossfadeControllerImpl?((cloudTheme == nil || cloudTheme?.settings != nil) && !crossfadeAccentColors)
})
}
openAccentColorPickerImpl = { [weak controller] themeReference, create in
if let _ = controller?.navigationController?.viewControllers.first(where: { $0 is ThemeAccentColorController }) {
return
}
let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: themeReference, create: create))
pushControllerImpl?(controller)
}
selectAccentColorImpl = { accentColor in
var wallpaperSignal: Signal<TelegramWallpaper?, NoError> = .single(nil)
if let colorWallpaper = accentColor?.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 _ = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)).start()
return .single(wallpaper)
} else {
return .single(nil)
}
}
}
let _ = (wallpaperSignal
|> deliverOnMainQueue).start(next: { presetWallpaper 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
}
let generalThemeReference: PresentationThemeReference
if case let .cloud(theme) = currentTheme, let settings = theme.theme.settings {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = currentTheme
}
currentTheme = generalThemeReference
var updatedTheme = current.theme
var updatedAutomaticThemeSwitchSetting = current.automaticThemeSwitchSetting
if autoNightModeTriggered {
updatedAutomaticThemeSwitchSetting.theme = generalThemeReference
} else {
updatedTheme = generalThemeReference
}
guard let _ = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.color, wallpaper: presetWallpaper, baseColor: accentColor?.baseColor) else {
return current
}
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var themeSpecificAccentColors = current.themeSpecificAccentColors
themeSpecificAccentColors[generalThemeReference.index] = accentColor?.withUpdatedWallpaper(presetWallpaper)
if case .builtin = generalThemeReference {
let index = coloredThemeIndex(reference: currentTheme, accentColor: accentColor)
if let wallpaper = current.themeSpecificChatWallpapers[index] {
if wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin {
themeSpecificChatWallpapers[index] = presetWallpaper
}
} else {
themeSpecificChatWallpapers[index] = presetWallpaper
}
}
return PresentationThemeSettings(theme: updatedTheme, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: updatedAutomaticThemeSwitchSetting, largeEmoji: current.largeEmoji, reduceMotion: current.reduceMotion)
}).start()
presentCrossfadeControllerImpl?(true)
})
}
moreImpl = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_CreateTheme, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = (context.sharedContext.accountManager.transaction { transaction -> PresentationThemeReference in
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
let themeReference: PresentationThemeReference
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered
if autoNightModeTriggered {
themeReference = settings.automaticThemeSwitchSetting.theme
} else {
themeReference = settings.theme
}
return themeReference
}
|> deliverOnMainQueue).start(next: { themeReference in
let controller = editThemeController(context: context, mode: .create(nil, nil), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
pushControllerImpl?(controller)
})
}))
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)
}
return controller
}
public final class ThemeSettingsCrossfadeController: ViewController {
private var snapshotView: UIView?
private var topSnapshotView: UIView?
private var bottomSnapshotView: UIView?
private var sideSnapshotView: UIView?
fileprivate var didAppear: (() -> Void)?
public init(view: UIView? = nil, topOffset: CGFloat? = nil, bottomOffset: CGFloat? = nil, leftOffset: CGFloat? = nil) {
if let view = view {
if var leftOffset = leftOffset {
leftOffset += UIScreenPixel
if let view = view.snapshotView(afterScreenUpdates: false) {
let clipView = UIView()
clipView.clipsToBounds = true
clipView.addSubview(view)
view.clipsToBounds = true
view.contentMode = .topLeft
if let topOffset = topOffset, let bottomOffset = bottomOffset {
var frame = view.frame
frame.origin.y = topOffset
frame.size.width = leftOffset
frame.size.height = bottomOffset - topOffset
clipView.frame = frame
frame = view.frame
frame.origin.y = -topOffset
frame.size.width = leftOffset
frame.size.height = bottomOffset
view.frame = frame
}
self.sideSnapshotView = clipView
}
}
if let view = view.snapshotView(afterScreenUpdates: false) {
view.clipsToBounds = true
view.contentMode = .top
if let topOffset = topOffset {
var frame = view.frame
frame.size.height = topOffset
view.frame = frame
}
self.topSnapshotView = view
}
if let view = view.snapshotView(afterScreenUpdates: false) {
view.clipsToBounds = true
view.contentMode = .bottom
if let bottomOffset = bottomOffset {
var frame = view.frame
frame.origin.y = bottomOffset
frame.size.height -= bottomOffset
view.frame = frame
}
self.bottomSnapshotView = view
}
} else {
self.snapshotView = UIScreen.main.snapshotView(afterScreenUpdates: false)
}
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func loadDisplayNode() {
self.displayNode = ViewControllerTracingNode()
self.displayNode.backgroundColor = nil
self.displayNode.isOpaque = false
self.displayNode.isUserInteractionEnabled = false
if let snapshotView = self.snapshotView {
self.displayNode.view.addSubview(snapshotView)
}
if let topSnapshotView = self.topSnapshotView {
self.displayNode.view.addSubview(topSnapshotView)
}
if let bottomSnapshotView = self.bottomSnapshotView {
self.displayNode.view.addSubview(bottomSnapshotView)
}
if let sideSnapshotView = self.sideSnapshotView {
self.displayNode.view.addSubview(sideSnapshotView)
}
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
})
self.didAppear?()
}
}