mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
612 lines
29 KiB
Swift
612 lines
29 KiB
Swift
import Foundation
|
|
import Postbox
|
|
import SwiftSignalKit
|
|
import TelegramApi
|
|
|
|
#if os(macOS)
|
|
let telegramThemeFormat = "macos"
|
|
let telegramThemeFileExtension = "palette"
|
|
#else
|
|
let telegramThemeFormat = "ios"
|
|
let telegramThemeFileExtension = "tgios-theme"
|
|
#endif
|
|
|
|
public func telegramThemes(postbox: Postbox, network: Network, accountManager: AccountManager<TelegramAccountManagerTypes>?, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> {
|
|
let fetch: ([TelegramTheme]?, Int64?) -> Signal<[TelegramTheme], NoError> = { current, hash in
|
|
network.request(Api.functions.account.getThemes(format: telegramThemeFormat, hash: hash ?? 0))
|
|
|> retryRequest
|
|
|> mapToSignal { result -> Signal<([TelegramTheme], Int64), NoError> in
|
|
switch result {
|
|
case let .themes(hash, themes):
|
|
let result = themes.compactMap { TelegramTheme(apiTheme: $0) }
|
|
if result == current {
|
|
return .complete()
|
|
} else {
|
|
return .single((result, hash))
|
|
}
|
|
case .themesNotModified:
|
|
return .complete()
|
|
}
|
|
}
|
|
|> mapToSignal { items, hash -> Signal<[TelegramTheme], NoError> in
|
|
if let accountManager = accountManager {
|
|
let _ = accountManager.transaction { transaction in
|
|
transaction.updateSharedData(SharedDataKeys.themeSettings, { current in
|
|
var updated = current?.get(ThemeSettings.self) ?? ThemeSettings(currentTheme: nil)
|
|
for theme in items {
|
|
if theme.id == updated.currentTheme?.id {
|
|
updated = ThemeSettings(currentTheme: theme)
|
|
break
|
|
}
|
|
}
|
|
return PreferencesEntry(updated)
|
|
})
|
|
}.start()
|
|
}
|
|
|
|
return postbox.transaction { transaction -> [TelegramTheme] in
|
|
var entries: [OrderedItemListEntry] = []
|
|
for item in items {
|
|
var intValue = Int32(entries.count)
|
|
let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4))
|
|
if let entry = CodableEntry(TelegramThemeNativeCodable(item)) {
|
|
entries.append(OrderedItemListEntry(id: id, contents: entry))
|
|
}
|
|
}
|
|
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: entries)
|
|
if let entry = CodableEntry(CachedThemesConfiguration(hash: hash)) {
|
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedThemesConfiguration, key: ValueBoxKey(length: 0)), entry: entry)
|
|
}
|
|
return items
|
|
}
|
|
} |> then(
|
|
postbox.combinedView(keys: [PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudThemes)])
|
|
|> map { view -> [TelegramTheme] in
|
|
if let view = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudThemes)] as? OrderedItemListView {
|
|
return view.items.compactMap { $0.contents.get(TelegramThemeNativeCodable.self)?.value }
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
if forceUpdate {
|
|
return fetch(nil, nil)
|
|
} else {
|
|
return postbox.transaction { transaction -> ([TelegramTheme], Int64?) in
|
|
let configuration = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedThemesConfiguration, key: ValueBoxKey(length: 0)))?.get(CachedThemesConfiguration.self)
|
|
let items = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
|
return (items.compactMap { $0.contents.get(TelegramThemeNativeCodable.self)?.value }, configuration?.hash)
|
|
}
|
|
|> mapToSignal { current, hash -> Signal<[TelegramTheme], NoError> in
|
|
return .single(current)
|
|
|> then(fetch(current, hash))
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum GetThemeError {
|
|
case generic
|
|
case unsupported
|
|
case slugInvalid
|
|
}
|
|
|
|
public func getTheme(account: Account, slug: String) -> Signal<TelegramTheme, GetThemeError> {
|
|
return account.network.request(Api.functions.account.getTheme(format: telegramThemeFormat, theme: .inputThemeSlug(slug: slug)))
|
|
|> mapError { error -> GetThemeError in
|
|
if error.errorDescription == "THEME_FORMAT_INVALID" {
|
|
return .unsupported
|
|
}
|
|
if error.errorDescription == "THEME_SLUG_INVALID" {
|
|
return .slugInvalid
|
|
}
|
|
return .generic
|
|
}
|
|
|> mapToSignal { theme -> Signal<TelegramTheme, GetThemeError> in
|
|
return .single(TelegramTheme(apiTheme: theme))
|
|
}
|
|
}
|
|
|
|
public enum ThemeUpdatedResult {
|
|
case updated(TelegramTheme)
|
|
case notModified
|
|
}
|
|
|
|
private func checkThemeUpdated(network: Network, theme: TelegramTheme) -> Signal<ThemeUpdatedResult, GetThemeError> {
|
|
return network.request(Api.functions.account.getTheme(format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash)))
|
|
|> mapError { _ -> GetThemeError in return .generic }
|
|
|> map { theme -> ThemeUpdatedResult in
|
|
return .updated(TelegramTheme(apiTheme: theme))
|
|
}
|
|
}
|
|
|
|
private func saveUnsaveTheme(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: TelegramTheme, unsave: Bool) -> Signal<Void, NoError> {
|
|
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
|
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
|
var items = entries.compactMap { $0.contents.get(TelegramThemeNativeCodable.self)?.value }
|
|
items = items.filter { $0.id != theme.id }
|
|
if !unsave {
|
|
items.insert(theme, at: 0)
|
|
}
|
|
var updatedEntries: [OrderedItemListEntry] = []
|
|
for item in items {
|
|
var intValue = Int32(updatedEntries.count)
|
|
let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4))
|
|
if let entry = CodableEntry(TelegramThemeNativeCodable(item)) {
|
|
updatedEntries.append(OrderedItemListEntry(id: id, contents: entry))
|
|
}
|
|
}
|
|
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
|
|
|
|
return account.network.request(Api.functions.account.saveTheme(theme: Api.InputTheme.inputTheme(id: theme.id, accessHash: theme.accessHash), unsave: unsave ? Api.Bool.boolTrue : Api.Bool.boolFalse))
|
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|
return .complete()
|
|
}
|
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
|
return telegramThemes(postbox: account.postbox, network: account.network, accountManager: accountManager, forceUpdate: true)
|
|
|> take(1)
|
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
|
return .complete()
|
|
}
|
|
}
|
|
} |> switchToLatest
|
|
}
|
|
|
|
private func installTheme(account: Account, theme: TelegramTheme?, baseTheme: TelegramBaseTheme? = nil, autoNight: Bool) -> Signal<Never, NoError> {
|
|
var flags: Int32 = 0
|
|
if autoNight {
|
|
flags |= 1 << 0
|
|
}
|
|
|
|
let inputTheme: Api.InputTheme?
|
|
if let theme = theme {
|
|
inputTheme = .inputTheme(id: theme.id, accessHash: theme.accessHash)
|
|
flags |= 1 << 1
|
|
} else {
|
|
inputTheme = nil
|
|
}
|
|
|
|
flags |= 1 << 2
|
|
|
|
let inputBaseTheme: Api.BaseTheme?
|
|
if let baseTheme = baseTheme {
|
|
inputBaseTheme = baseTheme.apiBaseTheme
|
|
} else {
|
|
inputBaseTheme = nil
|
|
}
|
|
|
|
return account.network.request(Api.functions.account.installTheme(flags: flags, theme: inputTheme, format: telegramThemeFormat, baseTheme: inputBaseTheme))
|
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|
return .complete()
|
|
}
|
|
|> mapToSignal { _ -> Signal<Never, NoError> in
|
|
return .complete()
|
|
}
|
|
}
|
|
|
|
public enum UploadThemeResult {
|
|
case progress(Float)
|
|
case complete(TelegramMediaFile)
|
|
}
|
|
|
|
public enum UploadThemeError {
|
|
case generic
|
|
}
|
|
|
|
private struct UploadedThemeData {
|
|
fileprivate let content: UploadedThemeDataContent
|
|
}
|
|
|
|
private enum UploadedThemeDataContent {
|
|
case result(MultipartUploadResult)
|
|
case error
|
|
}
|
|
|
|
private func uploadedTheme(postbox: Postbox, network: Network, resource: MediaResource) -> Signal<UploadedThemeData, NoError> {
|
|
return multipartUpload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .file, userContentType: .file), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|
|
|> map { result -> UploadedThemeData in
|
|
return UploadedThemeData(content: .result(result))
|
|
}
|
|
|> `catch` { _ -> Signal<UploadedThemeData, NoError> in
|
|
return .single(UploadedThemeData(content: .error))
|
|
}
|
|
}
|
|
|
|
private func uploadedThemeThumbnail(postbox: Postbox, network: Network, data: Data) -> Signal<UploadedThemeData, NoError> {
|
|
return multipartUpload(network: network, postbox: postbox, source: .data(data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|
|
|> map { result -> UploadedThemeData in
|
|
return UploadedThemeData(content: .result(result))
|
|
}
|
|
|> `catch` { _ -> Signal<UploadedThemeData, NoError> in
|
|
return .single(UploadedThemeData(content: .error))
|
|
}
|
|
}
|
|
|
|
private func uploadTheme(account: Account, resource: MediaResource, thumbnailData: Data? = nil) -> Signal<UploadThemeResult, UploadThemeError> {
|
|
let fileName = "theme.\(telegramThemeFileExtension)"
|
|
let mimeType = "application/x-tgtheme-\(telegramThemeFormat)"
|
|
|
|
let uploadedThumbnail: Signal<UploadedThemeData?, UploadThemeError>
|
|
if let thumbnailData = thumbnailData {
|
|
uploadedThumbnail = uploadedThemeThumbnail(postbox: account.postbox, network: account.network, data: thumbnailData)
|
|
|> mapError { _ -> UploadThemeError in }
|
|
|> map(Optional.init)
|
|
} else {
|
|
uploadedThumbnail = .single(nil)
|
|
}
|
|
|
|
return uploadedThumbnail
|
|
|> mapToSignal { thumbnailResult -> Signal<UploadThemeResult, UploadThemeError> in
|
|
return uploadedTheme(postbox: account.postbox, network: account.network, resource: resource)
|
|
|> mapError { _ -> UploadThemeError in }
|
|
|> mapToSignal { result -> Signal<UploadThemeResult, UploadThemeError> in
|
|
switch result.content {
|
|
case .error:
|
|
return .fail(.generic)
|
|
case let .result(resultData):
|
|
switch resultData {
|
|
case let .progress(progress):
|
|
return .single(.progress(progress))
|
|
case let .inputFile(file):
|
|
var flags: Int32 = 0
|
|
var thumbnailFile: Api.InputFile?
|
|
if let thumbnailResult = thumbnailResult?.content, case let .result(result) = thumbnailResult, case let .inputFile(file) = result {
|
|
thumbnailFile = file
|
|
flags |= 1 << 0
|
|
}
|
|
return account.network.request(Api.functions.account.uploadTheme(flags: flags, file: file, thumb: thumbnailFile, fileName: fileName, mimeType: mimeType))
|
|
|> mapError { _ in return UploadThemeError.generic }
|
|
|> mapToSignal { document -> Signal<UploadThemeResult, UploadThemeError> in
|
|
if let file = telegramMediaFileFromApiDocument(document, altDocuments: []) {
|
|
return .single(.complete(file))
|
|
} else {
|
|
return .fail(.generic)
|
|
}
|
|
}
|
|
default:
|
|
return .fail(.generic)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum CreateThemeError {
|
|
case generic
|
|
case slugInvalid
|
|
case slugOccupied
|
|
}
|
|
|
|
public enum CreateThemeResult {
|
|
case result(TelegramTheme)
|
|
case progress(Float)
|
|
}
|
|
|
|
public func createTheme(account: Account, title: String, resource: MediaResource? = nil, thumbnailData: Data? = nil, settings: [TelegramThemeSettings]?) -> Signal<CreateThemeResult, CreateThemeError> {
|
|
var flags: Int32 = 0
|
|
|
|
var inputSettings: [Api.InputThemeSettings]?
|
|
if let _ = resource {
|
|
flags |= 1 << 2
|
|
}
|
|
if let settings = settings {
|
|
flags |= 1 << 3
|
|
inputSettings = settings.map { $0.apiInputThemeSettings }
|
|
}
|
|
|
|
if let resource = resource {
|
|
return uploadTheme(account: account, resource: resource, thumbnailData: thumbnailData)
|
|
|> mapError { _ in return CreateThemeError.generic }
|
|
|> mapToSignal { result -> Signal<CreateThemeResult, CreateThemeError> in
|
|
switch result {
|
|
case let .complete(file):
|
|
if let resource = file.resource as? CloudDocumentMediaResource {
|
|
return account.network.request(Api.functions.account.createTheme(flags: flags, slug: "", title: title, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), settings: inputSettings))
|
|
|> mapError { error in
|
|
if error.errorDescription == "THEME_SLUG_INVALID" {
|
|
return .slugInvalid
|
|
} else if error.errorDescription == "THEME_SLUG_OCCUPIED" {
|
|
return .slugOccupied
|
|
}
|
|
return .generic
|
|
}
|
|
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
|
|
let theme = TelegramTheme(apiTheme: apiTheme)
|
|
return account.postbox.transaction { transaction -> CreateThemeResult in
|
|
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
|
var items = entries.compactMap { $0.contents.get(TelegramThemeNativeCodable.self)?.value }
|
|
items.insert(theme, at: 0)
|
|
var updatedEntries: [OrderedItemListEntry] = []
|
|
for item in items {
|
|
var intValue = Int32(updatedEntries.count)
|
|
let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4))
|
|
if let entry = CodableEntry(TelegramThemeNativeCodable(item)) {
|
|
updatedEntries.append(OrderedItemListEntry(id: id, contents: entry))
|
|
}
|
|
}
|
|
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
|
|
return .result(theme)
|
|
}
|
|
|> castError(CreateThemeError.self)
|
|
}
|
|
}
|
|
else {
|
|
return .fail(.generic)
|
|
}
|
|
case let .progress(progress):
|
|
return .single(.progress(progress))
|
|
}
|
|
}
|
|
} else {
|
|
return account.network.request(Api.functions.account.createTheme(flags: flags, slug: "", title: title, document: .inputDocumentEmpty, settings: inputSettings))
|
|
|> mapError { error in
|
|
if error.errorDescription == "THEME_SLUG_INVALID" {
|
|
return .slugInvalid
|
|
} else if error.errorDescription == "THEME_SLUG_OCCUPIED" {
|
|
return .slugOccupied
|
|
}
|
|
return .generic
|
|
}
|
|
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
|
|
var theme = TelegramTheme(apiTheme: apiTheme)
|
|
theme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, emoticon: nil, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
|
|
|
|
return account.postbox.transaction { transaction -> CreateThemeResult in
|
|
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
|
var items = entries.compactMap { $0.contents.get(TelegramThemeNativeCodable.self)?.value }
|
|
items.insert(theme, at: 0)
|
|
var updatedEntries: [OrderedItemListEntry] = []
|
|
for item in items {
|
|
var intValue = Int32(updatedEntries.count)
|
|
let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4))
|
|
if let entry = CodableEntry(TelegramThemeNativeCodable(item)) {
|
|
updatedEntries.append(OrderedItemListEntry(id: id, contents: entry))
|
|
}
|
|
}
|
|
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
|
|
return .result(theme)
|
|
}
|
|
|> castError(CreateThemeError.self)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func updateTheme(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil, settings: [TelegramThemeSettings]?) -> Signal<CreateThemeResult, CreateThemeError> {
|
|
guard title != nil || slug != nil || resource != nil else {
|
|
return .complete()
|
|
}
|
|
var flags: Int32 = 0
|
|
if let slug = slug, !slug.isEmpty {
|
|
flags |= 1 << 0
|
|
}
|
|
if let _ = title {
|
|
flags |= 1 << 1
|
|
}
|
|
if let _ = resource {
|
|
flags |= 1 << 2
|
|
}
|
|
var inputSettings: [Api.InputThemeSettings]?
|
|
if let settings = settings {
|
|
flags |= 1 << 3
|
|
inputSettings = settings.map { $0.apiInputThemeSettings }
|
|
}
|
|
let uploadSignal: Signal<UploadThemeResult?, UploadThemeError>
|
|
if let resource = resource {
|
|
uploadSignal = uploadTheme(account: account, resource: resource, thumbnailData: thumbnailData)
|
|
|> map(Optional.init)
|
|
} else {
|
|
uploadSignal = .single(nil)
|
|
}
|
|
return uploadSignal
|
|
|> mapError { _ -> CreateThemeError in
|
|
return .generic
|
|
}
|
|
|> mapToSignal { result -> Signal<CreateThemeResult, CreateThemeError> in
|
|
let inputDocument: Api.InputDocument?
|
|
if let status = result {
|
|
switch status {
|
|
case let .complete(file):
|
|
if let resource = file.resource as? CloudDocumentMediaResource {
|
|
inputDocument = .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))
|
|
} else {
|
|
return .fail(.generic)
|
|
}
|
|
case let .progress(progress):
|
|
return .single(.progress(progress))
|
|
}
|
|
} else {
|
|
inputDocument = nil
|
|
}
|
|
|
|
return account.network.request(Api.functions.account.updateTheme(flags: flags, format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument, settings: inputSettings))
|
|
|> mapError { error in
|
|
if error.errorDescription == "THEME_SLUG_INVALID" {
|
|
return .slugInvalid
|
|
} else if error.errorDescription == "THEME_SLUG_OCCUPIED" {
|
|
return .slugOccupied
|
|
}
|
|
return .generic
|
|
}
|
|
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
|
|
let theme = TelegramTheme(apiTheme: apiTheme)
|
|
let updatedTheme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, emoticon: nil, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount)
|
|
|
|
let _ = accountManager.transaction { transaction in
|
|
transaction.updateSharedData(SharedDataKeys.themeSettings, { current in
|
|
var updated = current?.get(ThemeSettings.self) ?? ThemeSettings(currentTheme: nil)
|
|
if updatedTheme.id == updated.currentTheme?.id {
|
|
updated = ThemeSettings(currentTheme: updatedTheme)
|
|
}
|
|
return PreferencesEntry(updated)
|
|
})
|
|
}.start()
|
|
return account.postbox.transaction { transaction -> CreateThemeResult in
|
|
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
|
let items = entries.map { entry -> TelegramTheme in
|
|
let theme = entry.contents.get(TelegramThemeNativeCodable.self)!.value
|
|
if theme.id == updatedTheme.id {
|
|
return updatedTheme
|
|
} else {
|
|
return theme
|
|
}
|
|
}
|
|
var updatedEntries: [OrderedItemListEntry] = []
|
|
for item in items {
|
|
var intValue = Int32(updatedEntries.count)
|
|
let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4))
|
|
if let entry = CodableEntry(TelegramThemeNativeCodable(item)) {
|
|
updatedEntries.append(OrderedItemListEntry(id: id, contents: entry))
|
|
}
|
|
}
|
|
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
|
|
return .result(updatedTheme)
|
|
}
|
|
|> castError(CreateThemeError.self)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func saveThemeInteractively(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: TelegramTheme) -> Signal<Void, NoError> {
|
|
return saveUnsaveTheme(account: account, accountManager: accountManager, theme: theme, unsave: false)
|
|
}
|
|
|
|
public func deleteThemeInteractively(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: TelegramTheme) -> Signal<Void, NoError> {
|
|
return saveUnsaveTheme(account: account, accountManager: accountManager, theme: theme, unsave: true)
|
|
}
|
|
|
|
public func applyTheme(accountManager: AccountManager<TelegramAccountManagerTypes>, account: Account, theme: TelegramTheme?, autoNight: Bool = false) -> Signal<Never, NoError> {
|
|
return accountManager.transaction { transaction -> Signal<Never, NoError> in
|
|
transaction.updateSharedData(SharedDataKeys.themeSettings, { _ in
|
|
return PreferencesEntry(ThemeSettings(currentTheme: theme))
|
|
})
|
|
|
|
if let theme = theme {
|
|
return installTheme(account: account, theme: theme, autoNight: autoNight)
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
|> switchToLatest
|
|
}
|
|
|
|
func managedThemesUpdates(accountManager: AccountManager<TelegramAccountManagerTypes>, postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
|
let currentTheme = Atomic<TelegramTheme?>(value: nil)
|
|
return accountManager.sharedData(keys: [SharedDataKeys.themeSettings])
|
|
|> map { sharedData -> TelegramTheme? in
|
|
let themeSettings = sharedData.entries[SharedDataKeys.themeSettings]?.get(ThemeSettings.self) ?? ThemeSettings(currentTheme: nil)
|
|
return themeSettings.currentTheme
|
|
}
|
|
|> filter { theme in
|
|
return theme?.id != currentTheme.with({ $0 })?.id
|
|
}
|
|
|> mapToSignal { theme -> Signal<Void, NoError> in
|
|
let _ = currentTheme.swap(theme)
|
|
if let theme = theme {
|
|
let poll = Signal<Void, NoError> { subscriber in
|
|
let actualTheme = currentTheme.with { $0 } ?? theme
|
|
return checkThemeUpdated(network: network, theme: actualTheme).start(next: { result in
|
|
if case let .updated(updatedTheme) = result {
|
|
let _ = currentTheme.swap(theme)
|
|
let _ = accountManager.transaction { transaction in
|
|
transaction.updateSharedData(SharedDataKeys.themeSettings, { _ in
|
|
return PreferencesEntry(ThemeSettings(currentTheme: updatedTheme))
|
|
})
|
|
}.start()
|
|
let _ = postbox.transaction { transaction in
|
|
let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes)
|
|
|
|
var success = true
|
|
var mappedItems: [TelegramTheme] = []
|
|
for entry in entries {
|
|
if let theme = entry.contents.get(TelegramThemeNativeCodable.self)?.value {
|
|
if theme.id == updatedTheme.id {
|
|
mappedItems.append(updatedTheme)
|
|
} else {
|
|
mappedItems.append(theme)
|
|
}
|
|
} else {
|
|
success = false
|
|
break
|
|
}
|
|
}
|
|
if success {
|
|
var updatedEntries: [OrderedItemListEntry] = []
|
|
for item in mappedItems {
|
|
var intValue = Int32(updatedEntries.count)
|
|
let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4))
|
|
if let entry = CodableEntry(TelegramThemeNativeCodable(item)) {
|
|
updatedEntries.append(OrderedItemListEntry(id: id, contents: entry))
|
|
}
|
|
}
|
|
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
|
|
} else {
|
|
let _ = (telegramThemes(postbox: postbox, network: network, accountManager: accountManager, forceUpdate: true)
|
|
|> take(1)).start()
|
|
}
|
|
}.start()
|
|
}
|
|
subscriber.putCompletion()
|
|
})
|
|
}
|
|
return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func areThemesEqual(_ lhs: TelegramTheme, _ rhs: TelegramTheme) -> Bool {
|
|
if lhs.title != rhs.title {
|
|
return false
|
|
}
|
|
if lhs.slug != rhs.slug {
|
|
return false
|
|
}
|
|
if lhs.file?.id != rhs.file?.id {
|
|
return false
|
|
}
|
|
if lhs.settings != rhs.settings {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public func actualizedTheme(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: TelegramTheme) -> Signal<TelegramTheme, NoError> {
|
|
var currentTheme = theme
|
|
return accountManager.sharedData(keys: [SharedDataKeys.themeSettings])
|
|
|> mapToSignal { sharedData -> Signal<TelegramTheme, NoError> in
|
|
let themeSettings = sharedData.entries[SharedDataKeys.themeSettings]?.get(ThemeSettings.self) ?? ThemeSettings(currentTheme: nil)
|
|
if let updatedTheme = themeSettings.currentTheme, updatedTheme.id == theme.id {
|
|
if !areThemesEqual(updatedTheme, currentTheme) {
|
|
currentTheme = updatedTheme
|
|
return .single(updatedTheme)
|
|
} else {
|
|
return .single(currentTheme)
|
|
}
|
|
} else {
|
|
return account.postbox.combinedView(keys: [PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudThemes)])
|
|
|> map { view -> [TelegramTheme] in
|
|
if let view = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudThemes)] as? OrderedItemListView {
|
|
return view.items.compactMap { $0.contents.get(TelegramThemeNativeCodable.self)?.value }
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|> map { themes -> TelegramTheme in
|
|
let updatedTheme = themes.first(where: { $0.id == theme.id })
|
|
if let updatedTheme = updatedTheme {
|
|
if !areThemesEqual(updatedTheme, currentTheme) {
|
|
currentTheme = updatedTheme
|
|
return updatedTheme
|
|
} else {
|
|
return currentTheme
|
|
}
|
|
} else {
|
|
return currentTheme
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|