Swiftgram/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift
2021-12-30 17:24:59 +04:00

596 lines
29 KiB
Swift

import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import TelegramStringFormatting
import AccountContext
import DeviceLocationManager
import Geocoding
import WallpaperResources
import Sunrise
private enum TriggerMode {
case system
case none
case timeBased
case brightness
}
private enum TimeBasedManualField {
case from
case to
}
private final class ThemeAutoNightSettingsControllerArguments {
let context: AccountContext
let updateMode: (TriggerMode) -> Void
let updateTimeBasedAutomatic: (Bool) -> Void
let openTimeBasedManual: (TimeBasedManualField) -> Void
let updateTimeBasedAutomaticLocation: () -> Void
let updateAutomaticBrightness: (Double) -> Void
let updateTheme: (PresentationThemeReference) -> Void
init(context: AccountContext, updateMode: @escaping (TriggerMode) -> Void, updateTimeBasedAutomatic: @escaping (Bool) -> Void, openTimeBasedManual: @escaping (TimeBasedManualField) -> Void, updateTimeBasedAutomaticLocation: @escaping () -> Void, updateAutomaticBrightness: @escaping (Double) -> Void, updateTheme: @escaping (PresentationThemeReference) -> Void) {
self.context = context
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 modeSystem(PresentationTheme, String, Bool)
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 themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper])
var section: ItemListSectionId {
switch self {
case .modeSystem, .modeDisabled, .modeTimeBased, .modeBrightness:
return ThemeAutoNightSettingsControllerSection.mode.rawValue
case .settingsHeader, .timeBasedAutomaticLocation, .timeBasedAutomaticLocationValue, .timeBasedManualFrom, .timeBasedManualTo, .brightnessValue, .settingInfo:
return ThemeAutoNightSettingsControllerSection.settings.rawValue
case .themeHeader, .themeItem:
return ThemeAutoNightSettingsControllerSection.theme.rawValue
}
}
var stableId: Int32 {
switch self {
case .modeSystem:
return 0
case .modeDisabled:
return 1
case .modeTimeBased:
return 2
case .modeBrightness:
return 3
case .settingsHeader:
return 4
case .timeBasedAutomaticLocation:
return 5
case .timeBasedAutomaticLocationValue:
return 6
case .timeBasedManualFrom:
return 7
case .timeBasedManualTo:
return 8
case .brightnessValue:
return 9
case .settingInfo:
return 10
case .themeHeader:
return 11
case .themeItem:
return 12
}
}
static func ==(lhs: ThemeAutoNightSettingsControllerEntry, rhs: ThemeAutoNightSettingsControllerEntry) -> Bool {
switch lhs {
case let .modeSystem(lhsTheme, lhsTitle, lhsValue):
if case let .modeSystem(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
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 .themeItem(lhsTheme, lhsStrings, lhsThemes, lhsAllThemes, lhsCurrentTheme, lhsThemeAccentColors, lhsThemeChatWallpapers):
if case let .themeItem(rhsTheme, rhsStrings, rhsThemes, rhsAllThemes, rhsCurrentTheme, rhsThemeAccentColors, rhsThemeChatWallpapers) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsThemes == rhsThemes, lhsAllThemes == rhsAllThemes, lhsCurrentTheme == rhsCurrentTheme, lhsThemeAccentColors == rhsThemeAccentColors, lhsThemeChatWallpapers == rhsThemeChatWallpapers {
return true
} else {
return false
}
}
}
static func <(lhs: ThemeAutoNightSettingsControllerEntry, rhs: ThemeAutoNightSettingsControllerEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ThemeAutoNightSettingsControllerArguments
switch self {
case let .modeSystem(_, title, value):
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateMode(.system)
})
case let .modeDisabled(_, title, value):
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateMode(.none)
})
case let .modeTimeBased(_, title, value):
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateMode(.timeBased)
})
case let .modeBrightness(_, title, value):
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateMode(.brightness)
})
case let .settingsHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .timeBasedAutomaticLocation(_, title, value):
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateTimeBasedAutomatic(value)
})
case let .timeBasedAutomaticLocationValue(_, title, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, titleColor: .accent, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
arguments.updateTimeBasedAutomaticLocation()
})
case let .timeBasedManualFrom(_, title, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openTimeBasedManual(.from)
})
case let .timeBasedManualTo(_, title, value):
return ItemListDisclosureItem(presentationData: presentationData, 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(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .themeHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, 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: false, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, themePreferredBaseTheme: [:], currentTheme: currentTheme, updatedTheme: { theme in
arguments.updateTheme(theme)
}, contextAction: nil)
}
}
}
private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, strings: PresentationStrings, settings: PresentationThemeSettings, switchSetting: AutomaticThemeSwitchSetting, availableThemes: [PresentationThemeReference], dateTimeFormat: PresentationDateTimeFormat) -> [ThemeAutoNightSettingsControllerEntry] {
var entries: [ThemeAutoNightSettingsControllerEntry] = []
let activeTriggerMode: TriggerMode
switch switchSetting.trigger {
case .system:
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
activeTriggerMode = .system
} else {
activeTriggerMode = .none
}
case .explicitNone:
activeTriggerMode = .none
case .timeBased:
activeTriggerMode = .timeBased
case .brightness:
activeTriggerMode = .brightness
}
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
entries.append(.modeSystem(theme, strings.AutoNightTheme_System, activeTriggerMode == .system))
}
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 .system, .explicitNone:
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(latitude, longitude, localizedName):
let calculator = EDSunriseSet(date: Date(), timezone: TimeZone.current, latitude: latitude, longitude: longitude)!
let sunset = roundTimeToDay(Int32(calculator.sunset.timeIntervalSince1970))
let sunrise = roundTimeToDay(Int32(calculator.sunrise.timeIntervalSince1970))
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)).string))
}
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))").string.replacingOccurrences(of: "%%", with: "%")))
}
switch switchSetting.trigger {
case .explicitNone:
break
case .system, .timeBased, .brightness:
entries.append(.themeHeader(theme, strings.AutoNightTheme_PreferredTheme))
let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
if case let .cloud(theme) = reference {
return theme.theme.settings == nil
} else {
return true
}
}
entries.append(.themeItem(theme, strings, generalThemes, availableThemes, switchSetting.theme, settings.themeSpecificAccentColors, settings.themeSpecificChatWallpapers))
}
return entries
}
private func roundTimeToDay(_ timestamp: Int32) -> Int32 {
let calendar = Calendar.current
let offset = 0
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 .system, .explicitNone, .brightness:
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
}
}
}
public func themeAutoNightSettingsController(context: AccountContext) -> ViewController {
var presentControllerImpl: ((ViewController) -> Void)?
let actionsDisposable = DisposableSet()
let updateAutomaticBrightnessDisposable = MetaDisposable()
let stagingSettingsPromise = ValuePromise<AutomaticThemeSwitchSetting?>(nil)
let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
let updateLocationDisposable = MetaDisposable()
actionsDisposable.add(updateLocationDisposable)
let updateSettings: (@escaping (AutomaticThemeSwitchSetting) -> AutomaticThemeSwitchSetting) -> Void = { f in
let _ = (combineLatest(stagingSettingsPromise.get(), sharedData)
|> take(1)
|> deliverOnMainQueue).start(next: { stagingSettings, sharedData in
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
let updated = f(stagingSettings ?? settings.automaticThemeSwitchSetting)
stagingSettingsPromise.set(updated)
if areSettingsValid(updated) {
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
var current = current
current.automaticThemeSwitchSetting = updated
return current
}).start()
}
})
}
let forceUpdateLocation: () -> Void = {
let locationCoordinates = Signal<(Double, Double), NoError> { subscriber in
return context.sharedContext.locationManager!.push(mode: DeviceLocationMode.preciseForeground, updated: { location, _ in
subscriber.putNext((location.coordinate.latitude, location.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?.city ?? "")
}
}
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 {
settings.trigger = .timeBased(setting: .automatic(latitude: location.0, longitude: location.1, localizedName: location.2))
}
return settings
}
})
updateLocationDisposable.set(disposable)
}
let arguments = ThemeAutoNightSettingsControllerArguments(context: context, updateMode: { mode in
var updateLocation = false
updateSettings { settings in
var settings = settings
switch mode {
case .system:
settings.trigger = .system
case .none:
settings.trigger = .explicitNone
case .timeBased:
if case .timeBased = settings.trigger {
} else {
settings.trigger = .timeBased(setting: .automatic(latitude: 0.0, longitude: 0.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, 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
}
presentControllerImpl?(ThemeAutoNightTimeSelectionActionSheet(context: context, 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
guard let presentationTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: theme) else {
return
}
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)
}
let _ = (resolvedWallpaper
|> mapToSignal { resolvedWallpaper -> Signal<Void, NoError> in
var updatedTheme = theme
if case let .cloud(info) = theme {
updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: info.theme.isCreator ? context.account.id : nil))
}
updateSettings { settings in
var settings = settings
settings.theme = updatedTheme
return settings
}
return .complete()
}).start()
})
let cloudThemes = Promise<[TelegramTheme]>()
let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager)
cloudThemes.set(updatedCloudThemes)
let signal = combineLatest(context.sharedContext.presentationData |> deliverOnMainQueue, sharedData |> deliverOnMainQueue, cloudThemes.get() |> deliverOnMainQueue, stagingSettingsPromise.get() |> deliverOnMainQueue)
|> map { presentationData, sharedData, cloudThemes, stagingSettings -> (ItemListControllerState, (ItemListNodeState, Any)) in
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
let defaultThemes: [PresentationThemeReference] = [.builtin(.night), .builtin(.nightAccent)]
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? context.account.id : nil)) }
var availableThemes = defaultThemes
availableThemes.append(contentsOf: cloudThemes)
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.AutoNightTheme_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeAutoNightSettingsControllerEntries(theme: presentationData.theme, strings: presentationData.strings, settings: settings, switchSetting: stagingSettings ?? settings.automaticThemeSwitchSetting, availableThemes: availableThemes, dateTimeFormat: presentationData.dateTimeFormat), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
controller.alwaysSynchronous = true
presentControllerImpl = { [weak controller] c in
controller?.present(c, in: .window(.root))
}
return controller
}