Swiftgram/TelegramUI/ThemeAutoNightSettingsController.swift
Ilya Laktyushin d20523252f Web Search: recent search queries
Various Fixes
2018-12-11 23:38:59 +04:00

539 lines
26 KiB
Swift

import Foundation
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramUIPrivateModule
private enum TriggerMode {
case none
case timeBased
case brightness
}
private enum TimeBasedManualField {
case from
case to
}
private final class ThemeAutoNightSettingsControllerArguments {
let updateMode: (TriggerMode) -> Void
let updateTimeBasedAutomatic: (Bool) -> Void
let openTimeBasedManual: (TimeBasedManualField) -> Void
let updateTimeBasedAutomaticLocation: () -> Void
let updateAutomaticBrightness: (Double) -> Void
let updateTheme: (PresentationBuiltinThemeReference) -> Void
init(updateMode: @escaping (TriggerMode) -> Void, updateTimeBasedAutomatic: @escaping (Bool) -> Void, openTimeBasedManual: @escaping (TimeBasedManualField) -> Void, updateTimeBasedAutomaticLocation: @escaping () -> Void, updateAutomaticBrightness: @escaping (Double) -> Void, updateTheme: @escaping (PresentationBuiltinThemeReference) -> Void) {
self.updateMode = updateMode
self.updateTimeBasedAutomatic = updateTimeBasedAutomatic
self.openTimeBasedManual = openTimeBasedManual
self.updateTimeBasedAutomaticLocation = updateTimeBasedAutomaticLocation
self.updateAutomaticBrightness = updateAutomaticBrightness
self.updateTheme = updateTheme
}
}
private enum ThemeAutoNightSettingsControllerSection: Int32 {
case mode
case settings
case theme
}
private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry {
case modeDisabled(PresentationTheme, String, Bool)
case modeTimeBased(PresentationTheme, String, Bool)
case modeBrightness(PresentationTheme, String, Bool)
case settingsHeader(PresentationTheme, String)
case timeBasedAutomaticLocation(PresentationTheme, String, Bool)
case timeBasedAutomaticLocationValue(PresentationTheme, String, String)
case timeBasedManualFrom(PresentationTheme, String, String)
case timeBasedManualTo(PresentationTheme, String, String)
case brightnessValue(PresentationTheme, Double)
case settingInfo(PresentationTheme, String)
case themeHeader(PresentationTheme, String)
case themeNightBlue(PresentationTheme, String, Bool)
case themeNight(PresentationTheme, String, Bool)
var section: ItemListSectionId {
switch self {
case .modeDisabled, .modeTimeBased, .modeBrightness:
return ThemeAutoNightSettingsControllerSection.mode.rawValue
case .settingsHeader, .timeBasedAutomaticLocation, .timeBasedAutomaticLocationValue, .timeBasedManualFrom, .timeBasedManualTo, .brightnessValue, .settingInfo:
return ThemeAutoNightSettingsControllerSection.settings.rawValue
case .themeHeader, .themeNightBlue, .themeNight:
return ThemeAutoNightSettingsControllerSection.theme.rawValue
}
}
var stableId: Int32 {
switch self {
case .modeDisabled:
return 0
case .modeTimeBased:
return 1
case .modeBrightness:
return 2
case .settingsHeader:
return 3
case .timeBasedAutomaticLocation:
return 4
case .timeBasedAutomaticLocationValue:
return 5
case .timeBasedManualFrom:
return 6
case .timeBasedManualTo:
return 7
case .brightnessValue:
return 8
case .settingInfo:
return 9
case .themeHeader:
return 10
case .themeNightBlue:
return 11
case .themeNight:
return 12
}
}
static func ==(lhs: ThemeAutoNightSettingsControllerEntry, rhs: ThemeAutoNightSettingsControllerEntry) -> Bool {
switch lhs {
case let .modeDisabled(lhsTheme, lhsTitle, lhsValue):
if case let .modeDisabled(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .modeTimeBased(lhsTheme, lhsTitle, lhsValue):
if case let .modeTimeBased(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .modeBrightness(lhsTheme, lhsTitle, lhsValue):
if case let .modeBrightness(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .settingsHeader(lhsTheme, lhsTitle):
if case let .settingsHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
case let .timeBasedAutomaticLocation(lhsTheme, lhsTitle, lhsValue):
if case let .timeBasedAutomaticLocation(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .timeBasedAutomaticLocationValue(lhsTheme, lhsTitle, lhsValue):
if case let .timeBasedAutomaticLocationValue(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .timeBasedManualFrom(lhsTheme, lhsTitle, lhsValue):
if case let .timeBasedManualFrom(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .timeBasedManualTo(lhsTheme, lhsTitle, lhsValue):
if case let .timeBasedManualTo(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .brightnessValue(lhsTheme, lhsValue):
if case let .brightnessValue(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
return true
} else {
return false
}
case let .settingInfo(lhsTheme, lhsValue):
if case let .settingInfo(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
return true
} else {
return false
}
case let .themeHeader(lhsTheme, lhsValue):
if case let .themeHeader(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
return true
} else {
return false
}
case let .themeNightBlue(lhsTheme, lhsTitle, lhsValue):
if case let .themeNightBlue(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
case let .themeNight(lhsTheme, lhsTitle, lhsValue):
if case let .themeNight(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
}
}
static func <(lhs: ThemeAutoNightSettingsControllerEntry, rhs: ThemeAutoNightSettingsControllerEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(_ arguments: ThemeAutoNightSettingsControllerArguments) -> ListViewItem {
switch self {
case let .modeDisabled(theme, title, value):
return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateMode(.none)
})
case let .modeTimeBased(theme, title, value):
return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateMode(.timeBased)
})
case let .modeBrightness(theme, title, value):
return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateMode(.brightness)
})
case let .settingsHeader(theme, title):
return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section)
case let .timeBasedAutomaticLocation(theme, title, value):
return ItemListSwitchItem(theme: theme, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateTimeBasedAutomatic(value)
})
case let .timeBasedAutomaticLocationValue(theme, title, value):
return ItemListDisclosureItem(theme: theme, icon: nil, title: title, titleColor: .accent, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
arguments.updateTimeBasedAutomaticLocation()
})
case let .timeBasedManualFrom(theme, title, value):
return ItemListDisclosureItem(theme: theme, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openTimeBasedManual(.from)
})
case let .timeBasedManualTo(theme, title, value):
return ItemListDisclosureItem(theme: theme, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openTimeBasedManual(.to)
})
case let .brightnessValue(theme, value):
return ThemeSettingsBrightnessItem(theme: theme, value: Int32(value * 100.0), sectionId: self.section, updated: { value in
arguments.updateAutomaticBrightness(Double(value) / 100.0)
})
case let .settingInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .themeHeader(theme, title):
return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section)
case let .themeNightBlue(theme, title, value):
return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateTheme(.nightAccent)
})
case let .themeNight(theme, title, value):
return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateTheme(.nightGrayscale)
})
}
}
}
private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, strings: PresentationStrings, switchSetting: AutomaticThemeSwitchSetting, dateTimeFormat: PresentationDateTimeFormat) -> [ThemeAutoNightSettingsControllerEntry] {
var entries: [ThemeAutoNightSettingsControllerEntry] = []
let activeTriggerMode: TriggerMode
switch switchSetting.trigger {
case .none:
activeTriggerMode = .none
case .timeBased:
activeTriggerMode = .timeBased
case .brightness:
activeTriggerMode = .brightness
}
entries.append(.modeDisabled(theme, strings.AutoNightTheme_Disabled, activeTriggerMode == .none))
entries.append(.modeTimeBased(theme, strings.AutoNightTheme_Scheduled, activeTriggerMode == .timeBased))
entries.append(.modeBrightness(theme, strings.AutoNightTheme_Automatic, activeTriggerMode == .brightness))
switch switchSetting.trigger {
case .none:
break
case let .timeBased(setting):
entries.append(.settingsHeader(theme, strings.AutoNightTheme_ScheduleSection))
var automaticLocation = false
if case .automatic = setting {
automaticLocation = true
}
entries.append(.timeBasedAutomaticLocation(theme, strings.AutoNightTheme_UseSunsetSunrise, automaticLocation))
switch setting {
case let .automatic(_, _, sunset, sunrise, localizedName):
entries.append(.timeBasedAutomaticLocationValue(theme, strings.AutoNightTheme_UpdateLocation, localizedName))
if sunset != 0 || sunrise != 0 {
entries.append(.settingInfo(theme, strings.AutoNightTheme_LocationHelp(stringForMessageTimestamp(timestamp: sunset, dateTimeFormat: dateTimeFormat, local: false), stringForMessageTimestamp(timestamp: sunrise, dateTimeFormat: dateTimeFormat, local: false)).0))
}
case let .manual(fromSeconds, toSeconds):
entries.append(.timeBasedManualFrom(theme, strings.AutoNightTheme_ScheduledFrom, stringForMessageTimestamp(timestamp: fromSeconds, dateTimeFormat: dateTimeFormat, local: false)))
entries.append(.timeBasedManualTo(theme, strings.AutoNightTheme_ScheduledTo, stringForMessageTimestamp(timestamp: toSeconds, dateTimeFormat: dateTimeFormat, local: false)))
}
case let .brightness(threshold):
entries.append(.settingsHeader(theme, strings.AutoNightTheme_AutomaticSection))
entries.append(.brightnessValue(theme, threshold))
entries.append(.settingInfo(theme, strings.AutoNightTheme_AutomaticHelp("\(Int(threshold * 100.0))").0.replacingOccurrences(of: "%%", with: "%")))
}
switch switchSetting.trigger {
case .none:
break
case .timeBased, .brightness:
entries.append(.themeHeader(theme, strings.AutoNightTheme_PreferredTheme))
entries.append(.themeNightBlue(theme, strings.Appearance_ThemeNightBlue, switchSetting.theme == .nightAccent))
entries.append(.themeNight(theme, strings.Appearance_ThemeNight, switchSetting.theme == .nightGrayscale))
}
return entries
}
private func roundTimeToDay(_ timestamp: Int32) -> Int32 {
let calendar = Calendar.current
let offset = 0//TimeZone.current.secondsFromGMT(for: Date())
let components = calendar.dateComponents([.hour, .minute, .second], from: Date(timeIntervalSince1970: Double(timestamp + Int32(offset))))
return Int32(components.hour! * 60 * 60 + components.minute! * 60 + components.second!)
}
private func areSettingsValid(_ settings: AutomaticThemeSwitchSetting) -> Bool {
switch settings.trigger {
case .none:
return true
case let .timeBased(setting):
switch setting {
case let .automatic(latitude, longitude, _, _, _):
if !latitude.isZero || !longitude.isZero {
return true
} else {
return false
}
case .manual:
return true
}
case .brightness:
return true
}
}
public func themeAutoNightSettingsController(account: Account) -> ViewController {
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController) -> Void)?
let actionsDisposable = DisposableSet()
let updateAutomaticBrightnessDisposable = MetaDisposable()
let stagingSettingsPromise = ValuePromise<AutomaticThemeSwitchSetting?>(nil)
let themeSettingsKey = ApplicationSpecificPreferencesKeys.presentationThemeSettings
let localizationSettingsKey = PreferencesKeys.localizationSettings
let preferences = account.postbox.preferencesView(keys: [themeSettingsKey, localizationSettingsKey])
let updateLocationDisposable = MetaDisposable()
actionsDisposable.add(updateLocationDisposable)
let updateSettings: (@escaping (AutomaticThemeSwitchSetting) -> AutomaticThemeSwitchSetting) -> Void = { f in
let _ = (combineLatest(stagingSettingsPromise.get(), preferences)
|> take(1)
|> deliverOnMainQueue).start(next: { stagingSettings, preferences in
let settings = (preferences.values[themeSettingsKey] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
let updated = f(stagingSettings ?? settings.automaticThemeSwitchSetting)
stagingSettingsPromise.set(updated)
if areSettingsValid(updated) {
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
var current = current
current.automaticThemeSwitchSetting = updated
return current
}).start()
}
})
}
let forceUpdateLocation: () -> Void = {
let locationCoordinates = Signal<(Double, Double), NoError> { subscriber in
return account.telegramApplicationContext.locationManager!.push(mode: DeviceLocationMode.precise, updated: { coordinate in
subscriber.putNext((coordinate.latitude, coordinate.longitude))
subscriber.putCompletion()
})
}
let geocodedLocation = locationCoordinates
|> mapToSignal { coordinates -> Signal<(Double, Double, String), NoError> in
return reverseGeocodeLocation(latitude: coordinates.0, longitude: coordinates.1)
|> map { locality in
return (coordinates.0, coordinates.1, locality)
}
}
let disposable = (geocodedLocation
|> take(1)
|> deliverOnMainQueue).start(next: { location in
updateSettings { settings in
var settings = settings
if case let .timeBased(setting) = settings.trigger, case .automatic = setting {
let calculator = EDSunriseSet(date: Date(), timezone: TimeZone.current, latitude: location.0, longitude: location.1)!
let sunset = roundTimeToDay(Int32(calculator.sunset.timeIntervalSince1970))
let sunrise = roundTimeToDay(Int32(calculator.sunrise.timeIntervalSince1970))
settings.trigger = .timeBased(setting: .automatic(latitude: location.0, longitude: location.1, sunset: sunset, sunrise: sunrise, localizedName: location.2))
}
return settings
}
})
updateLocationDisposable.set(disposable)
}
let arguments = ThemeAutoNightSettingsControllerArguments(updateMode: { mode in
var updateLocation = false
updateSettings { settings in
var settings = settings
switch mode {
case .none:
settings.trigger = .none
case .timeBased:
if case .timeBased = settings.trigger {
} else {
settings.trigger = .timeBased(setting: .automatic(latitude: 0.0, longitude: 0.0, sunset: 0, sunrise: 0, localizedName: ""))
updateLocation = true
}
case .brightness:
if case .brightness = settings.trigger {
} else {
settings.trigger = .brightness(threshold: 0.2)
}
}
if updateLocation {
forceUpdateLocation()
}
return settings
}
}, updateTimeBasedAutomatic: { value in
var updateLocation = false
updateSettings { settings in
var settings = settings
if case let .timeBased(setting) = settings.trigger {
switch setting {
case .automatic:
if !value {
settings.trigger = .timeBased(setting: .manual(fromSeconds: 22 * 60 * 60, toSeconds: 9 * 60 * 60))
}
case .manual:
if value {
settings.trigger = .timeBased(setting: .automatic(latitude: 0.0, longitude: 0.0, sunset: 0, sunrise: 0, localizedName: ""))
updateLocation = true
}
}
}
if updateLocation {
forceUpdateLocation()
}
return settings
}
}, openTimeBasedManual: { field in
var currentValue: Int32
switch field {
case .from:
currentValue = 22 * 60 * 60
case .to:
currentValue = 9 * 60 * 60
}
updateSettings { settings in
let settings = settings
switch settings.trigger {
case let .timeBased(setting):
switch setting {
case let .manual(fromSeconds, toSeconds):
switch field {
case .from:
currentValue = fromSeconds
case .to:
currentValue = toSeconds
}
default:
break
}
default:
break
}
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
presentControllerImpl?(ThemeAutoNightTimeSelectionActionSheet(account: account, currentValue: currentValue, applyValue: { value in
guard let value = value else {
return
}
updateSettings { settings in
var settings = settings
switch settings.trigger {
case let .timeBased(setting):
switch setting {
case var .manual(fromSeconds, toSeconds):
switch field {
case .from:
fromSeconds = value
case .to:
toSeconds = value
}
settings.trigger = .timeBased(setting: .manual(fromSeconds: fromSeconds, toSeconds: toSeconds))
default:
break
}
default:
break
}
return settings
}
}))
return settings
}
}, updateTimeBasedAutomaticLocation: {
forceUpdateLocation()
}, updateAutomaticBrightness: { value in
updateAutomaticBrightnessDisposable.set((Signal<Never, NoError>.complete()
|> delay(0.1, queue: Queue.mainQueue())).start(completed: {
updateSettings { settings in
var settings = settings
switch settings.trigger {
case .brightness:
settings.trigger = .brightness(threshold: max(0.0, min(1.0, value)))
default:
break
}
return settings
}
}))
}, updateTheme: { theme in
updateSettings { settings in
var settings = settings
settings.theme = theme
return settings
}
})
let signal = combineLatest(account.telegramApplicationContext.presentationData, preferences, stagingSettingsPromise.get())
|> deliverOnMainQueue
|> map { presentationData, preferences, stagingSettings -> (ItemListControllerState, (ItemListNodeState<ThemeAutoNightSettingsControllerEntry>, ThemeAutoNightSettingsControllerEntry.ItemGenerationArguments)) in
let settings = (preferences.values[themeSettingsKey] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.AutoNightTheme_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: themeAutoNightSettingsControllerEntries(theme: presentationData.theme, strings: presentationData.strings, switchSetting: stagingSettings ?? settings.automaticThemeSwitchSetting, dateTimeFormat: presentationData.dateTimeFormat), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(account: account, state: signal)
pushControllerImpl = { [weak controller] c in
(controller?.navigationController as? NavigationController)?.pushViewController(c)
}
presentControllerImpl = { [weak controller] c in
controller?.present(c, in: .window(.root))
}
return controller
}