From a706852dfc2fc8b2afed8482601552149a7ca5b8 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 29 Aug 2019 13:17:35 +0300 Subject: [PATCH] Cloud themes improvements --- .../Sources/Themes/EditThemeController.swift | 111 +++++++++++++----- .../Themes/ThemeSettingsController.swift | 63 ++++++---- .../Themes/ThemeSettingsThemeItem.swift | 33 +++++- .../AccountStateManagementUtils.swift | 12 +- .../TelegramCore/TelegramCore/Themes.swift | 96 ++++++++++++--- .../CreateThemeIcon.imageset/Contents.json | 12 ++ .../ic_iphonetheme.pdf | Bin 0 -> 5768 bytes .../TelegramUI/ChatMediaInputGifPane.swift | 2 +- .../ChatMessageAnimatedStickerItemNode.swift | 2 +- .../ChatMessageBubbleItemNode.swift | 2 +- .../ChatMessageInstantVideoItemNode.swift | 11 +- .../ChatMessageStickerItemNode.swift | 2 +- .../Sources/PostboxKeys.swift | 2 + .../Sources/WallpaperResources.swift | 19 ++- 14 files changed, 279 insertions(+), 88 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/CreateThemeIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/CreateThemeIcon.imageset/ic_iphonetheme.pdf diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index 2988aeb84e..c64e0483a4 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -239,9 +239,9 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll statePromise.set(stateValue.modify { f($0) }) } - var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? var dismissImpl: (() -> Void)? + var dismissInputImpl: (() -> Void)? let arguments = EditThemeControllerArguments(context: context, updateState: { f in updateState(f) @@ -285,6 +285,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll } rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: isComplete, action: { + dismissInputImpl?() arguments.updateState { current in var state = current state.updating = true @@ -326,52 +327,106 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll } let themeResource: LocalFileMediaResource? - - if let theme = theme, let themeString = encodePresentationTheme(theme), let themeData = themeString.data(using: .utf8) { + let themeData: Data? + if let theme = theme, let themeString = encodePresentationTheme(theme), let data = themeString.data(using: .utf8) { let resource = LocalFileMediaResource(fileId: arc4random64()) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: themeData) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: themeData) + context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) themeResource = resource + themeData = data } else { themeResource = nil + themeData = nil } switch mode { case .create: if let themeResource = themeResource { let _ = (createTheme(account: context.account, resource: themeResource, title: state.title) - |> deliverOnMainQueue).start(error: { error in + |> deliverOnMainQueue).start(next: { next in + if case let .result(resultTheme) = next { + let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() + let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in + let current: PresentationThemeSettings + if let entry = entry as? PresentationThemeSettings { + current = entry + } else { + current = PresentationThemeSettings.defaultSettings + } + + if let resource = resultTheme.file?.resource, let data = themeData { + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + } + + let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: nil)) + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + if let theme = theme { + themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper + } + + return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + }) + } |> deliverOnMainQueue).start(completed: { + if !custom { + saveThemeTemplateFile(state.title, themeResource, { + dismissImpl?() + }) + } else { + dismissImpl?() + } + }) + } + }, error: { error in arguments.updateState { current in var state = current state.updating = false return state } - }, completed: { - if !custom { - saveThemeTemplateFile(state.title, themeResource, { - dismissImpl?() - }) - } else { - dismissImpl?() - } }) } - case let .edit(theme): - let _ = (updateTheme(account: context.account, theme: theme.theme, title: state.title, slug: state.slug, resource: themeResource) - |> deliverOnMainQueue).start(error: { error in + case let .edit(info): + let _ = (updateTheme(account: context.account, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource) + |> deliverOnMainQueue).start(next: { next in + if case let .result(resultTheme) = next { + let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() + let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in + let current: PresentationThemeSettings + if let entry = entry as? PresentationThemeSettings { + current = entry + } else { + current = PresentationThemeSettings.defaultSettings + } + + if let resource = resultTheme.file?.resource, let data = themeData { + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + } + + let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: nil)) + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + if let theme = theme { + themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper + } + + return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + }) + } |> deliverOnMainQueue).start(completed: { + if let themeResource = themeResource, !custom { + saveThemeTemplateFile(state.title, themeResource, { + dismissImpl?() + }) + } else { + dismissImpl?() + } + }) + } + }, error: { error in arguments.updateState { current in var state = current state.updating = false return state } - }, completed: { - if let themeResource = themeResource, !custom { - saveThemeTemplateFile(state.title, themeResource, { - dismissImpl?() - }) - } else { - dismissImpl?() - } }) } }) @@ -395,9 +450,6 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll } let controller = ItemListController(context: context, state: signal) - 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) } @@ -405,5 +457,8 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll controller?.view.endEditing(true) let _ = controller?.dismiss() } + dismissInputImpl = { [weak controller] in + controller?.view.endEditing(true) + } return controller } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index f55e8585ea..2e54688f2e 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -45,8 +45,9 @@ private final class ThemeSettingsControllerArguments { let disableAnimations: (Bool) -> Void let selectAppIcon: (String) -> Void let presentThemeMenu: (PresentationThemeReference, Bool) -> Void + let editTheme: (PresentationCloudTheme) -> Void - init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, PresentationThemeAccentColor?) -> Void, openAutoNightTheme: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, presentThemeMenu: @escaping (PresentationThemeReference, Bool) -> Void) { + init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, PresentationThemeAccentColor?) -> Void, openAutoNightTheme: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, presentThemeMenu: @escaping (PresentationThemeReference, Bool) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void) { self.context = context self.selectTheme = selectTheme self.selectFontSize = selectFontSize @@ -58,6 +59,7 @@ private final class ThemeSettingsControllerArguments { self.disableAnimations = disableAnimations self.selectAppIcon = selectAppIcon self.presentThemeMenu = presentThemeMenu + self.editTheme = editTheme } } @@ -289,7 +291,13 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .themeItem(theme, strings, themes, currentTheme, themeSpecificAccentColors, currentColor): return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, themeSpecificAccentColors: themeSpecificAccentColors, currentTheme: currentTheme, updatedTheme: { theme in - arguments.selectTheme(theme) + if case let .cloud(theme) = theme, theme.theme.file == nil { + if theme.theme.isCreator { + arguments.editTheme(theme) + } + } else { + arguments.selectTheme(theme) + } }, longTapped: { theme in arguments.presentThemeMenu(theme, theme == currentTheme) }) @@ -485,24 +493,32 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveTheme, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() -// let _ = (cloudThemes.get() -// |> take(1) -// |> deliverOnMainQueue).start(next: { themes in -// if isCurrent, let themeIndex = themes.firstIndex(where: { $0.id == theme.id }) { -// let newTheme: PresentationThemeReference -// if themeIndex > 0 { -// newTheme = .cloud(themes[themeIndex - 1]) -// } else { -// newTheme = .builtin(.nightAccent) -// } -// selectThemeImpl?(newTheme) -// } -// -// let updatedThemes = themes.filter { $0.id != theme.id } -// cloudThemes.set(.single(updatedThemes) |> then(updatedCloudThemes)) -// -// let _ = (deleteTheme(account: context.account, theme: theme)).start() -// }) + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) + var items: [ActionSheetItem] = [] + items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeConfirmation, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + let _ = (cloudThemes.get() |> delay(0.5, queue: Queue.mainQueue()) + |> take(1) + |> deliverOnMainQueue).start(next: { themes in + if isCurrent, let themeIndex = themes.firstIndex(where: { $0.id == theme.theme.id }) { + let newTheme: PresentationThemeReference + if themeIndex > 0 { + newTheme = .cloud(PresentationCloudTheme(theme: themes[themeIndex - 1], resolvedWallpaper: nil)) + } else { + newTheme = .builtin(.nightAccent) + } + selectThemeImpl?(newTheme) + } + + let _ = deleteThemeInteractively(account: context.account, theme: theme.theme).start() + }) + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + presentControllerImpl?(actionSheet, nil) })) actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in @@ -510,6 +526,13 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }) ])]) presentControllerImpl?(actionSheet, nil) + }, 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))) + } + }) + presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }) let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), statePromise.get()) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift index 65533a9805..72f6822ef4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift @@ -41,6 +41,31 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec })?.stretchableImage(withLeftCapWidth: 15, topCapHeight: 15) } +private func createThemeImage(theme: PresentationTheme) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + return .single(theme) + |> map { theme -> (TransformImageArguments) -> DrawingContext? in + return { arguments in + let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) + let drawingRect = arguments.drawingRect + + context.withContext { c in + c.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor) + c.fill(drawingRect) + + c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0) + c.scaleBy(x: 1.0, y: -1.0) + c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0) + + if let icon = generateTintedImage(image: UIImage(bundleImageName: "Settings/CreateThemeIcon"), color: theme.list.itemAccentColor) { + c.draw(icon.cgImage!, in: CGRect(origin: CGPoint(x: floor((drawingRect.width - icon.size.width) / 2.0) - 3.0, y: floor((drawingRect.height - icon.size.height) / 2.0)), size: icon.size)) + } + } + + return context + } + } +} + private func themeIconImage(context: AccountContext, theme: PresentationThemeReference, accentColor: UIColor?) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal: Signal<(UIColor, UIColor, UIColor), NoError> if case let .builtin(theme) = theme { @@ -202,7 +227,11 @@ private final class ThemeSettingsThemeItemIconNode : ASDisplayNode { } func setup(context: AccountContext, theme: PresentationThemeReference, accentColor: UIColor?, currentTheme: PresentationTheme, title: NSAttributedString, bordered: Bool, selected: Bool, action: @escaping () -> Void, longTapAction: @escaping () -> Void) { - self.imageNode.setSignal(themeIconImage(context: context, theme: theme, accentColor: accentColor)) + if case let .cloud(theme) = theme, theme.theme.file == nil { + self.imageNode.setSignal(createThemeImage(theme: currentTheme)) + } else { + self.imageNode.setSignal(themeIconImage(context: context, theme: theme, accentColor: accentColor)) + } self.textNode.attributedText = title self.overlayNode.image = generateBorderImage(theme: currentTheme, bordered: bordered, selected: selected) self.action = { @@ -395,7 +424,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { self?.scrollToNode(imageNode, animated: true) } }, longTapAction: { - item.longTapped(theme) + item.longTapped(theme) }) imageNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 0.0), size: nodeSize) diff --git a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift index 2ea2c3afa4..ed73718a1b 100644 --- a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift @@ -2939,8 +2939,8 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP } if !updatedThemes.isEmpty { - let items = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) - let themes = items.map { entry -> TelegramTheme in + let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) + let themes = entries.map { entry -> TelegramTheme in let theme = entry.contents as! TelegramTheme if let updatedTheme = updatedThemes[theme.id] { return updatedTheme @@ -2948,13 +2948,13 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP return theme } } - var entries: [OrderedItemListEntry] = [] + var updatedEntries: [OrderedItemListEntry] = [] for theme in themes { - var intValue = Int32(entries.count) + var intValue = Int32(updatedEntries.count) let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4)) - entries.append(OrderedItemListEntry(id: id, contents: theme)) + updatedEntries.append(OrderedItemListEntry(id: id, contents: theme)) } - transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: entries) + transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries) } addedIncomingMessageIds.append(contentsOf: addedSecretMessageIds) diff --git a/submodules/TelegramCore/TelegramCore/Themes.swift b/submodules/TelegramCore/TelegramCore/Themes.swift index 057dbbfd07..87139785d5 100644 --- a/submodules/TelegramCore/TelegramCore/Themes.swift +++ b/submodules/TelegramCore/TelegramCore/Themes.swift @@ -62,7 +62,16 @@ public func telegramThemes(postbox: Postbox, network: Network, forceUpdate: Bool transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedThemesConfiguration, key: ValueBoxKey(length: 0)), entry: CachedThemesConfiguration(hash: hash), collectionSpec: ItemCacheCollectionSpec(lowWaterItemCount: 1, highWaterItemCount: 1)) 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 as? TelegramTheme } + } else { + return [] + } + } + ) } if forceUpdate { @@ -122,22 +131,35 @@ public func checkThemeUpdated(account: Account, theme: TelegramTheme) -> Signal< } } -public func saveTheme(account: Account, theme: TelegramTheme) -> Signal { - return saveUnsaveTheme(account: account, theme: theme, unsave: false) -} - -public func deleteTheme(account: Account, theme: TelegramTheme) -> Signal { - return saveUnsaveTheme(account: account, theme: theme, unsave: true) -} - private func saveUnsaveTheme(account: Account, theme: TelegramTheme, unsave: Bool) -> Signal { - 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 in - return .complete() - } - |> mapToSignal { _ -> Signal in - return .single(Void()) - } + return account.postbox.transaction { transaction -> Signal in + let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) + var items = entries.map { $0.contents as! TelegramTheme } + if unsave { + items = items.filter { $0.id != theme.id } + } else { + 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)) + updatedEntries.append(OrderedItemListEntry(id: id, contents: item)) + } + 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 in + return .complete() + } + |> mapToSignal { _ -> Signal in + return telegramThemes(postbox: account.postbox, network: account.network, forceUpdate: true) + |> take(1) + |> mapToSignal { _ -> Signal in + return .complete() + } + } + } |> switchToLatest } private func installTheme(account: Account, theme: TelegramTheme) -> Signal { @@ -234,7 +256,16 @@ public func createTheme(account: Account, resource: MediaResource, title: String |> mapToSignal { apiTheme -> Signal in if let theme = TelegramTheme(apiTheme: apiTheme) { return account.postbox.transaction { transaction -> CreateThemeResult in - + let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) + var items = entries.map { $0.contents as! TelegramTheme } + 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)) + updatedEntries.append(OrderedItemListEntry(id: id, contents: item)) + } + transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries) return .result(theme) } |> introduceError(CreateThemeError.self) @@ -295,8 +326,27 @@ public func updateTheme(account: Account, theme: TelegramTheme, title: String?, return account.network.request(Api.functions.account.updateTheme(flags: flags, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument)) |> mapError { _ in return CreateThemeError.generic } |> mapToSignal { apiTheme -> Signal in - if let theme = TelegramTheme(apiTheme: apiTheme) { - return .single(.result(theme)) + if let result = TelegramTheme(apiTheme: apiTheme) { + 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 as! TelegramTheme + if theme.id == result.id { + return result + } else { + return theme + } + } + var updatedEntries: [OrderedItemListEntry] = [] + for item in items { + var intValue = Int32(updatedEntries.count) + let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4)) + updatedEntries.append(OrderedItemListEntry(id: id, contents: item)) + } + transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries) + return .result(result) + } + |> introduceError(CreateThemeError.self) } else { return .fail(.generic) } @@ -336,6 +386,14 @@ public final class ThemeSettings: PreferencesEntry, Equatable { } } +public func saveThemeInteractively(account: Account, theme: TelegramTheme) -> Signal { + return saveUnsaveTheme(account: account, theme: theme, unsave: false) +} + +public func deleteThemeInteractively(account: Account, theme: TelegramTheme) -> Signal { + return saveUnsaveTheme(account: account, theme: theme, unsave: true) +} + public func applyTheme(accountManager: AccountManager, account: Account, theme: TelegramTheme?) -> Signal { return accountManager.transaction { transaction -> Signal in transaction.updateSharedData(SharedDataKeys.themeSettings, { _ in diff --git a/submodules/TelegramUI/Images.xcassets/Settings/CreateThemeIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/CreateThemeIcon.imageset/Contents.json new file mode 100644 index 0000000000..ec9f317445 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/CreateThemeIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_iphonetheme.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/CreateThemeIcon.imageset/ic_iphonetheme.pdf b/submodules/TelegramUI/Images.xcassets/Settings/CreateThemeIcon.imageset/ic_iphonetheme.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1ea5be507ed8c17825c2f9ea1d38b60cb84697c8 GIT binary patch literal 5768 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 J6t$GV{|DoUBtZZG literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputGifPane.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputGifPane.swift index 4c2ded5aa6..3b4d31123e 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputGifPane.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputGifPane.swift @@ -165,7 +165,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate { })) multiplexedNode.fileSelected = { [weak self] fileReference, sourceNode, sourceRect in - self?.controllerInteraction.sendGif(fileReference, sourceNode, sourceRect) + let _ = self?.controllerInteraction.sendGif(fileReference, sourceNode, sourceRect) } multiplexedNode.didScroll = { [weak self] offset, height in diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index dce649cccd..ff94def414 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -382,7 +382,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) var needShareButton = false - if isFailed { + if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { needShareButton = false } else if item.message.id.peerId == item.context.account.peerId { for attribute in item.content.firstMessage.attributes { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift index 0254aee299..c562bc373b 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift @@ -699,7 +699,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) var needShareButton = false - if isFailed { + if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { needShareButton = false } else if item.message.id.peerId == item.context.account.peerId { if let _ = sourceReference { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift index b49d6a8ec8..e1e3ddaed8 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -144,8 +144,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { avatarInset = 0.0 } + let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) + var needShareButton = false - if item.message.id.peerId == item.context.account.peerId { + if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { + needShareButton = false + } + else if item.message.id.peerId == item.context.account.peerId { for attribute in item.content.firstMessage.attributes { if let _ = attribute as? SourceReferenceMessageAttribute { needShareButton = true @@ -189,7 +194,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } var deliveryFailedInset: CGFloat = 0.0 - if item.content.firstMessage.flags.contains(.Failed) { + if isFailed { deliveryFailedInset += 24.0 } @@ -474,7 +479,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { strongSelf.replyInfoNode = nil } - if item.content.firstMessage.flags.contains(.Failed) { + if isFailed { let deliveryFailedNode: ChatMessageDeliveryFailedNode var isAppearing = false if let current = strongSelf.deliveryFailedNode { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift index b24d2612e6..bcdcc68b89 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift @@ -184,7 +184,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) var needShareButton = false - if isFailed { + if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { needShareButton = false } else if item.message.id.peerId == item.context.account.peerId { for attribute in item.content.firstMessage.attributes { diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift index eceae3dba9..1049722f83 100644 --- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift +++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift @@ -53,11 +53,13 @@ public struct ApplicationSpecificSharedDataKeys { private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 { case instantPageStoredState = 0 case cachedInstantPages = 1 + case resolvedWallpapers = 2 } public struct ApplicationSpecificItemCacheCollectionId { public static let instantPageStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.instantPageStoredState.rawValue) public static let cachedInstantPages = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedInstantPages.rawValue) + public static let resolvedWallpapers = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.resolvedWallpapers.rawValue) } private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 { diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index 99801b909c..3e2d4b639e 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -634,7 +634,6 @@ private func generateBackArrowImage(color: UIColor) -> UIImage? { } public func themeImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - return telegramThemeData(account: account, accountManager: accountManager, resource: fileReference.media.resource, synchronousLoad: synchronousLoad) |> map { data in let theme: PresentationTheme? @@ -649,12 +648,20 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef context.withFlippedContext { c in c.setBlendMode(.normal) if let theme = theme { - if case let .color(value) = theme.chat.defaultWallpaper { - c.setFillColor(UIColor(rgb: UInt32(bitPattern: value)).cgColor) - } else { - c.setFillColor(theme.chatList.backgroundColor.cgColor) + switch theme.chat.defaultWallpaper { + case .builtin: + if let filePath = frameworkBundle.path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg"), let image = UIImage(contentsOfFile: filePath), let cgImage = image.cgImage { + c.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: drawingRect.width, height: drawingRect.height)) + } + case let .color(value): + c.setFillColor(UIColor(rgb: UInt32(bitPattern: value)).cgColor) + c.fill(drawingRect) + case let .file(file): + c.setFillColor(theme.chatList.backgroundColor.cgColor) + c.fill(drawingRect) + default: + break } - c.fill(drawingRect) c.setFillColor(theme.rootController.navigationBar.backgroundColor.cgColor) c.fill(CGRect(origin: CGPoint(x: 0.0, y: drawingRect.height - 42.0), size: CGSize(width: drawingRect.width, height: 42.0)))