Update themes

This commit is contained in:
Ilya Laktyushin 2019-12-23 15:13:21 +03:00
parent 88436ccb5c
commit efacf13a9e
27 changed files with 4748 additions and 4300 deletions

View File

@ -4675,6 +4675,7 @@ Any member of this group will be able to see messages in the channel.";
"Appearance.ThemePreview.Chat.2.ReplyName" = "Bob Harris"; "Appearance.ThemePreview.Chat.2.ReplyName" = "Bob Harris";
"Appearance.ThemePreview.Chat.2.Text" = "Right side. And, uh, with intensity."; "Appearance.ThemePreview.Chat.2.Text" = "Right side. And, uh, with intensity.";
"Appearance.ThemePreview.Chat.3.Text" = "Is that everything? It seemed like he said quite a bit more than that. 😯"; "Appearance.ThemePreview.Chat.3.Text" = "Is that everything? It seemed like he said quite a bit more than that. 😯";
"Appearance.ThemePreview.Chat.3.TextWithLink" = "Is that everything? It seemed like he said [quite a bit more] than that. 😯";
"Appearance.ThemePreview.Chat.4.Text" = "For relaxing times, make it Suntory time. 😎"; "Appearance.ThemePreview.Chat.4.Text" = "For relaxing times, make it Suntory time. 😎";
"Appearance.ThemePreview.Chat.5.Text" = "He wants you to turn, look in camera. O.K.?"; "Appearance.ThemePreview.Chat.5.Text" = "He wants you to turn, look in camera. O.K.?";
@ -5226,3 +5227,6 @@ Any member of this group will be able to see messages in the channel.";
"PrivacySettings.WebSessions" = "Active Websites"; "PrivacySettings.WebSessions" = "Active Websites";
"Appearance.ShareThemeColor" = "Share"; "Appearance.ShareThemeColor" = "Share";
"Theme.ThemeChanged" = "Color Theme Changed";
"Theme.ThemeChangedText" = "You can change it back in\n[Settings > Appearance]().";

View File

@ -148,9 +148,9 @@ public struct ChatAvailableMessageActions {
} }
public enum WallpaperUrlParameter { public enum WallpaperUrlParameter {
case slug(String, WallpaperPresentationOptions, UIColor?, Int32?) case slug(String, WallpaperPresentationOptions, UIColor?, UIColor?, Int32?, Int32?)
case color(UIColor) case color(UIColor)
case gradient(UIColor, UIColor) case gradient(UIColor, UIColor, Int32?)
} }
public enum ResolvedUrl { public enum ResolvedUrl {

View File

@ -133,8 +133,11 @@ public final class AuthTransferScanScreen: ViewController {
if let navigationController = navigationController as? NavigationController { if let navigationController = navigationController as? NavigationController {
let activeSessionsContext = self.activeSessionsContext let activeSessionsContext = self.activeSessionsContext
self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: "Loggin Successful", text: "Telegram for macOS", cancel: "Terminate"), elevatedLayout: false, animateInAsReplacement: false, action: { value in self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: "Loggin Successful", text: "Telegram for macOS", cancel: "Terminate"), elevatedLayout: false, animateInAsReplacement: false, action: { value in
if !value, let session = session { if value == .undo, let session = session {
let _ = activeSessionsContext.remove(hash: session.hash).start() let _ = activeSessionsContext.remove(hash: session.hash).start()
return true
} else {
return false
} }
}), in: .window(.root)) }), in: .window(.root))

View File

@ -1198,11 +1198,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count)) let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count))
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: text), elevatedLayout: false, animateInAsReplacement: true, action: { shouldCommit in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: text), elevatedLayout: false, animateInAsReplacement: true, action: { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return false
} }
if shouldCommit { if value == .commit {
let context = strongSelf.context let context = strongSelf.context
let presentationData = strongSelf.presentationData let presentationData = strongSelf.presentationData
let progressSignal = Signal<Never, NoError> { subscriber in let progressSignal = Signal<Never, NoError> { subscriber in
@ -1230,7 +1230,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
} }
let _ = (signal let _ = (signal
|> deliverOnMainQueue).start() |> deliverOnMainQueue).start()
} else { return true
} else if value == .undo {
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!) strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!)
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
var state = state var state = state
@ -1240,7 +1241,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return state return state
}) })
self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!) self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!)
return true
} }
return false
}), in: .current) }), in: .current)
strongSelf.donePressed() strongSelf.donePressed()
@ -1310,11 +1313,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}) })
if value { if value {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .hidArchive(title: strongSelf.presentationData.strings.ChatList_UndoArchiveHiddenTitle, text: strongSelf.presentationData.strings.ChatList_UndoArchiveHiddenText, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] shouldCommit in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .hidArchive(title: strongSelf.presentationData.strings.ChatList_UndoArchiveHiddenTitle, text: strongSelf.presentationData.strings.ChatList_UndoArchiveHiddenText, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return false
} }
if !shouldCommit { if value == .undo {
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Bool in let _ = (strongSelf.context.account.postbox.transaction { transaction -> Bool in
var updatedValue = false var updatedValue = false
updateChatArchiveSettings(transaction: transaction, { settings in updateChatArchiveSettings(transaction: transaction, { settings in
@ -1325,10 +1328,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}) })
return updatedValue return updatedValue
}).start() }).start()
return true
} }
return false
}), in: .current) }), in: .current)
} else { } else {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .revealedArchive(title: strongSelf.presentationData.strings.ChatList_UndoArchiveRevealedTitle, text: strongSelf.presentationData.strings.ChatList_UndoArchiveRevealedText, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .revealedArchive(title: strongSelf.presentationData.strings.ChatList_UndoArchiveRevealedTitle, text: strongSelf.presentationData.strings.ChatList_UndoArchiveRevealedText, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false
}), in: .current) }), in: .current)
} }
}) })
@ -1422,11 +1427,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return true return true
}) })
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: strongSelf.presentationData.strings.Undo_ChatCleared), elevatedLayout: false, animateInAsReplacement: true, action: { shouldCommit in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: strongSelf.presentationData.strings.Undo_ChatCleared), elevatedLayout: false, animateInAsReplacement: true, action: { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return false
} }
if shouldCommit { if value == .commit {
let _ = clearHistoryInteractively(postbox: strongSelf.context.account.postbox, peerId: peerId, type: type).start(completed: { let _ = clearHistoryInteractively(postbox: strongSelf.context.account.postbox, peerId: peerId, type: type).start(completed: {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -1437,13 +1442,16 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return state return state
}) })
}) })
} else { return true
} else if value == .undo {
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
var state = state var state = state
state.pendingClearHistoryPeerIds.remove(peer.peerId) state.pendingClearHistoryPeerIds.remove(peer.peerId)
return state return state
}) })
return true
} }
return false
}), in: .current) }), in: .current)
} }
@ -1618,11 +1626,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
deleteSendMessageIntents(peerId: peerId) deleteSendMessageIntents(peerId: peerId)
} }
let action: (Bool) -> Void = { shouldCommit in let action: (UndoOverlayAction) -> Bool = { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return false
} }
if !shouldCommit { if value == .undo {
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds[0]) strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds[0])
let _ = (postbox.transaction { transaction -> Void in let _ = (postbox.transaction { transaction -> Void in
for peerId in peerIds { for peerId in peerIds {
@ -1635,6 +1643,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
} }
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
}) })
return true
} else {
return false
} }
} }
@ -1721,11 +1732,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return true return true
}) })
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] shouldCommit in self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return false
} }
if shouldCommit { if value == .commit {
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
if let channel = chatPeer as? TelegramChannel { if let channel = chatPeer as? TelegramChannel {
strongSelf.context.peerChannelMemberCategoriesContextsManager.externallyRemoved(peerId: channel.id, memberId: strongSelf.context.account.peerId) strongSelf.context.peerChannelMemberCategoriesContextsManager.externallyRemoved(peerId: channel.id, memberId: strongSelf.context.account.peerId)
@ -1744,7 +1755,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
deleteSendMessageIntents(peerId: peerId) deleteSendMessageIntents(peerId: peerId)
}) })
completion() completion()
} else { return true
} else if value == .undo {
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
var state = state var state = state
@ -1752,7 +1764,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return state return state
}) })
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
return true
} }
return false
}), in: .current) }), in: .current)
} }

View File

@ -529,7 +529,7 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa
guard let peer = peer, let user = user else { guard let peer = peer, let user = user else {
return return
} }
presentControllerImpl?(UndoOverlayController(presentationData: context.sharedContext.currentPresentationData.with { $0 }, content: .succeed(text: presentationData.strings.Channel_OwnershipTransfer_TransferCompleted(user.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).0), elevatedLayout: false, action: { _ in }), nil) presentControllerImpl?(UndoOverlayController(presentationData: context.sharedContext.currentPresentationData.with { $0 }, content: .succeed(text: presentationData.strings.Channel_OwnershipTransfer_TransferCompleted(user.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).0), elevatedLayout: false, action: { _ in return false }), nil)
}) })
} }

View File

@ -588,7 +588,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
clearDisposable.set((signal clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats))) statsPromise.set(.single(.result(resultStats)))
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil) presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), .current, nil)
})) }))
} }
@ -769,7 +769,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
clearDisposable.set((signal clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats))) statsPromise.set(.single(.result(resultStats)))
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil) presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), .current, nil)
})) }))
} }
@ -896,7 +896,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
clearDisposable.set((signal clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats))) statsPromise.set(.single(.result(resultStats)))
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil) presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), .current, nil)
})) }))
} }
} }

View File

@ -188,7 +188,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
} }
public enum EditThemeControllerMode: Equatable { public enum EditThemeControllerMode: Equatable {
case create(PresentationTheme?) case create(PresentationTheme?, TelegramThemeSettings?)
case edit(PresentationCloudTheme) case edit(PresentationCloudTheme)
} }
@ -264,17 +264,20 @@ private func editThemeControllerEntries(presentationData: PresentationData, stat
public func editThemeController(context: AccountContext, mode: EditThemeControllerMode, navigateToChat: ((PeerId) -> Void)? = nil, completion: ((PresentationThemeReference) -> Void)? = nil) -> ViewController { public func editThemeController(context: AccountContext, mode: EditThemeControllerMode, navigateToChat: ((PeerId) -> Void)? = nil, completion: ((PresentationThemeReference) -> Void)? = nil) -> ViewController {
let initialState: EditThemeControllerState let initialState: EditThemeControllerState
let previewThemePromise = Promise<PresentationTheme>() let previewThemePromise = Promise<PresentationTheme>()
let settingsPromise = Promise<TelegramThemeSettings?>(nil)
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
switch mode { switch mode {
case let .create(existingTheme): case let .create(existingTheme, settings):
let theme: PresentationTheme let theme: PresentationTheme
let wallpaper: TelegramWallpaper let wallpaper: TelegramWallpaper
if let existingTheme = existingTheme { if let existingTheme = existingTheme {
theme = existingTheme theme = existingTheme
wallpaper = theme.chat.defaultWallpaper wallpaper = theme.chat.defaultWallpaper
settingsPromise.set(.single(settings))
} else { } else {
theme = presentationData.theme theme = presentationData.theme
wallpaper = presentationData.chatWallpaper wallpaper = presentationData.chatWallpaper
settingsPromise.set(.single(nil))
} }
initialState = EditThemeControllerState(mode: mode, title: generateThemeName(accentColor: theme.rootController.navigationBar.buttonColor), slug: "", updatedTheme: nil, updating: false) initialState = EditThemeControllerState(mode: mode, title: generateThemeName(accentColor: theme.rootController.navigationBar.buttonColor), slug: "", updatedTheme: nil, updating: false)
previewThemePromise.set(.single(theme.withUpdated(name: "", defaultWallpaper: wallpaper))) previewThemePromise.set(.single(theme.withUpdated(name: "", defaultWallpaper: wallpaper)))
@ -292,6 +295,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
} else { } else {
previewThemePromise.set(.single(theme.withUpdated(name: nil, defaultWallpaper: info.resolvedWallpaper))) previewThemePromise.set(.single(theme.withUpdated(name: nil, defaultWallpaper: info.resolvedWallpaper)))
} }
settingsPromise.set(.single(info.theme.settings))
} else { } else {
previewThemePromise.set(.single(presentationData.theme.withUpdated(name: "", defaultWallpaper: presentationData.chatWallpaper))) previewThemePromise.set(.single(presentationData.theme.withUpdated(name: "", defaultWallpaper: presentationData.chatWallpaper)))
@ -313,17 +317,19 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
let arguments = EditThemeControllerArguments(context: context, updateState: { f in let arguments = EditThemeControllerArguments(context: context, updateState: { f in
updateState(f) updateState(f)
}, openColors: { }, openColors: {
let _ = (previewThemePromise.get() let _ = (combineLatest(queue: Queue.mainQueue(), previewThemePromise.get(), settingsPromise.get())
|> take(1) |> take(1)).start(next: { theme, previousSettings in
|> deliverOnMainQueue).start(next: { theme in
var controllerDismissImpl: (() -> Void)? var controllerDismissImpl: (() -> Void)?
let controller = ThemeAccentColorController(context: context, mode: .edit(theme: theme, wallpaper: nil, defaultThemeReference: nil, create: false, completion: { updatedTheme in let controller = ThemeAccentColorController(context: context, mode: .edit(theme: theme, wallpaper: nil, defaultThemeReference: nil, create: false, completion: { updatedTheme, settings in
updateState { current in updateState { current in
var state = current var state = current
previewThemePromise.set(.single(updatedTheme)) previewThemePromise.set(.single(updatedTheme))
state.updatedTheme = updatedTheme state.updatedTheme = updatedTheme
return state return state
} }
if previousSettings != nil {
settingsPromise.set(.single(settings))
}
controllerDismissImpl?() controllerDismissImpl?()
})) }))
controllerDismissImpl = { [weak controller] in controllerDismissImpl = { [weak controller] in
@ -371,6 +377,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
return state return state
} }
} }
settingsPromise.set(.single(nil))
} }
else { else {
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_FileReadError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_FileReadError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
@ -420,8 +427,8 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
return state return state
} }
let _ = (previewThemePromise.get() let _ = (combineLatest(queue: Queue.mainQueue(), previewThemePromise.get(), settingsPromise.get())
|> deliverOnMainQueue).start(next: { previewTheme in |> take(1)).start(next: { previewTheme, settings in
let saveThemeTemplateFile: (String, LocalFileMediaResource, @escaping () -> Void) -> Void = { title, resource, completion in let saveThemeTemplateFile: (String, LocalFileMediaResource, @escaping () -> Void) -> Void = { title, resource, completion in
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: resource.fileId), partialReference: nil, resource: resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: "application/x-tgtheme-ios", size: nil, attributes: [.FileName(fileName: "\(title).tgios-theme")]) let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: resource.fileId), partialReference: nil, resource: resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: "application/x-tgtheme-ios", size: nil, attributes: [.FileName(fileName: "\(title).tgios-theme")])
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil) let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
@ -494,7 +501,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
switch mode { switch mode {
case .create: case .create:
if let themeResource = themeResource { if let themeResource = themeResource {
let _ = (createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData) let _ = (createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData, settings: settings)
|> deliverOnMainQueue).start(next: { next in |> deliverOnMainQueue).start(next: { next in
if case let .result(resultTheme) = next { if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
@ -528,7 +535,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
}) })
} }
case let .edit(info): case let .edit(info):
let _ = (updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource) let _ = (updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource, settings: settings)
|> deliverOnMainQueue).start(next: { next in |> deliverOnMainQueue).start(next: { next in
if case let .result(resultTheme) = next { if case let .result(resultTheme) = next {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()

View File

@ -17,7 +17,7 @@ private let randomBackgroundColors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xe
enum ThemeAccentColorControllerMode { enum ThemeAccentColorControllerMode {
case colors(themeReference: PresentationThemeReference, create: Bool) case colors(themeReference: PresentationThemeReference, create: Bool)
case background(themeReference: PresentationThemeReference) case background(themeReference: PresentationThemeReference)
case edit(theme: PresentationTheme, wallpaper: TelegramWallpaper?, defaultThemeReference: PresentationThemeReference?, create: Bool, completion: (PresentationTheme) -> Void) case edit(theme: PresentationTheme, wallpaper: TelegramWallpaper?, defaultThemeReference: PresentationThemeReference?, create: Bool, completion: (PresentationTheme, TelegramThemeSettings?) -> Void)
var themeReference: PresentationThemeReference? { var themeReference: PresentationThemeReference? {
switch self { switch self {
@ -181,13 +181,25 @@ final class ThemeAccentColorController: ViewController {
let _ = (prepare let _ = (prepare
|> deliverOnMainQueue).start(completed: { [weak self] in |> deliverOnMainQueue).start(completed: { [weak self] in
let updatedTheme: PresentationTheme let updatedTheme: PresentationTheme
var settings: TelegramThemeSettings?
if let themeReference = themeReference { if let themeReference = themeReference {
updatedTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: state.accentColor, backgroundColors: state.backgroundColors, bubbleColors: state.messagesColors, wallpaper: state.initialWallpaper ?? coloredWallpaper, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme updatedTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: state.accentColor, backgroundColors: state.backgroundColors, bubbleColors: state.messagesColors, wallpaper: state.initialWallpaper ?? coloredWallpaper, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme
if case let .builtin(theme) = themeReference {
var messageColors: (Int32, Int32)?
if let colors = state.messagesColors {
messageColors = (Int32(bitPattern: colors.0.rgb), Int32(bitPattern: colors.1?.rgb ?? colors.0.rgb))
}
settings = TelegramThemeSettings(baseTheme: theme.baseTheme, accentColor: Int32(bitPattern: state.accentColor.rgb), messageColors: messageColors, wallpaper: coloredWallpaper)
}
} else { } else {
updatedTheme = customizePresentationTheme(theme, editing: false, accentColor: state.accentColor, backgroundColors: state.backgroundColors, bubbleColors: state.messagesColors, wallpaper: state.initialWallpaper ?? coloredWallpaper) updatedTheme = customizePresentationTheme(theme, editing: false, accentColor: state.accentColor, backgroundColors: state.backgroundColors, bubbleColors: state.messagesColors, wallpaper: state.initialWallpaper ?? coloredWallpaper)
} }
completion(updatedTheme) completion(updatedTheme, settings)
}) })
} else { } else {
let _ = (prepare let _ = (prepare

View File

@ -15,6 +15,7 @@ import WallpaperResources
import OverlayStatusController import OverlayStatusController
import AppBundle import AppBundle
import PresentationDataUtils import PresentationDataUtils
import UndoUI
public enum ThemePreviewSource { public enum ThemePreviewSource {
case settings(PresentationThemeReference, TelegramWallpaper?) case settings(PresentationThemeReference, TelegramWallpaper?)
@ -279,7 +280,7 @@ public final class ThemePreviewController: ViewController {
|> mapToSignal { themes -> Signal<PresentationThemeReference, NoError> in |> mapToSignal { themes -> Signal<PresentationThemeReference, NoError> in
let similarTheme = themes.first(where: { $0.isCreator && $0.title == info.title }) let similarTheme = themes.first(where: { $0.isCreator && $0.title == info.title })
if let similarTheme = similarTheme { if let similarTheme = similarTheme {
return updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: similarTheme, title: nil, slug: nil, resource: info.resource, thumbnailData: themeThumbnailData) return updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: similarTheme, title: nil, slug: nil, resource: info.resource, thumbnailData: themeThumbnailData, settings: nil)
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<CreateThemeResult?, NoError> in |> `catch` { _ -> Signal<CreateThemeResult?, NoError> in
return .single(nil) return .single(nil)
@ -298,7 +299,7 @@ public final class ThemePreviewController: ViewController {
} }
} else { } else {
return createTheme(account: context.account, title: info.title, resource: info.resource, thumbnailData: themeThumbnailData) return createTheme(account: context.account, title: info.title, resource: info.resource, thumbnailData: themeThumbnailData, settings: nil)
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<CreateThemeResult?, NoError> in |> `catch` { _ -> Signal<CreateThemeResult?, NoError> in
return .single(nil) return .single(nil)
@ -322,31 +323,52 @@ public final class ThemePreviewController: ViewController {
return .single(theme) return .single(theme)
} }
} }
|> mapToSignal { updatedTheme -> Signal<Void, NoError> in |> mapToSignal { updatedTheme -> Signal<(PresentationThemeReference, Bool)?, NoError> in
if case let .cloud(info) = updatedTheme { if case let .cloud(info) = updatedTheme {
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: info.theme).start() let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: info.theme).start()
let _ = saveThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme).start() let _ = saveThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme).start()
} }
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered let autoNightModeTriggered = context.sharedContext.currentPresentationData.with { $0 }.autoNightModeTriggered
return updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current -> PresentationThemeSettings in var switchingFromDefaultTheme = false
var updated: PresentationThemeSettings
if autoNightModeTriggered { return context.sharedContext.accountManager.transaction { transaction -> (PresentationThemeReference, Bool)? in
var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting var previousDefaultTheme: (PresentationThemeReference, Bool)?
automaticThemeSwitchSetting.theme = updatedTheme transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
updated = current.withUpdatedAutomaticThemeSwitchSetting(automaticThemeSwitchSetting) let currentSettings: PresentationThemeSettings
} else { if let entry = entry as? PresentationThemeSettings {
updated = current.withUpdatedTheme(updatedTheme) currentSettings = entry
} } else {
currentSettings = PresentationThemeSettings.defaultSettings
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers }
themeSpecificChatWallpapers[updatedTheme.index] = nil
return updated.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers) var updatedSettings: PresentationThemeSettings
}) if autoNightModeTriggered {
if case .builtin = currentSettings.automaticThemeSwitchSetting.theme {
previousDefaultTheme = (currentSettings.automaticThemeSwitchSetting.theme, true)
}
var automaticThemeSwitchSetting = currentSettings.automaticThemeSwitchSetting
automaticThemeSwitchSetting.theme = updatedTheme
updatedSettings = currentSettings.withUpdatedAutomaticThemeSwitchSetting(automaticThemeSwitchSetting)
} else {
if case .builtin = currentSettings.theme {
previousDefaultTheme = (currentSettings.theme, false)
}
updatedSettings = currentSettings.withUpdatedTheme(updatedTheme)
}
var themeSpecificChatWallpapers = updatedSettings.themeSpecificChatWallpapers
themeSpecificChatWallpapers[updatedTheme.index] = nil
return updatedSettings.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers)
})
return previousDefaultTheme
}
} }
var cancelImpl: (() -> Void)? var cancelImpl: (() -> Void)?
let progress = Signal<Never, NoError> { [weak self] subscriber in let progress = Signal<Never, NoError> { [weak self] subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?() cancelImpl?()
})) }))
self?.present(controller, in: .window(.root)) self?.present(controller, in: .window(.root))
@ -369,9 +391,34 @@ public final class ThemePreviewController: ViewController {
progressDisposable.dispose() progressDisposable.dispose()
} }
} }
|> deliverOnMainQueue).start(completed: {[weak self] in |> deliverOnMainQueue).start(next: { [weak self] previousDefaultTheme in
if let strongSelf = self { if let strongSelf = self {
Queue.mainQueue().after(0.3) { Queue.mainQueue().after(0.3) {
let navigationController = strongSelf.navigationController as? NavigationController
if let (previousDefaultTheme, autoNightMode) = previousDefaultTheme {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .actionSucceeded(title: strongSelf.presentationData.strings.Theme_ThemeChanged, text: strongSelf.presentationData.strings.Theme_ThemeChangedText, cancel: strongSelf.presentationData.strings.Undo_Undo), elevatedLayout: false, animateInAsReplacement: false, action: { value in
if value == .undo {
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current -> PresentationThemeSettings in
var updated: PresentationThemeSettings
if autoNightMode {
var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting
automaticThemeSwitchSetting.theme = previousDefaultTheme
updated = current.withUpdatedAutomaticThemeSwitchSetting(automaticThemeSwitchSetting)
} else {
updated = current.withUpdatedTheme(previousDefaultTheme)
}
return updated
}).start()
return true
} else if value == .info {
let controller = themeSettingsController(context: context)
controller.navigationPresentation = .modal
navigationController?.pushViewController(controller, animated: true)
return true
}
return false
}), in: .window(.root))
}
strongSelf.dismiss() strongSelf.dismiss()
} }
} }

View File

@ -200,8 +200,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
strongSelf.blurredNode.image = image strongSelf.blurredNode.image = image
strongSelf.blurredNode.blurView.blurRadius = 45.0 strongSelf.blurredNode.blurView.blurRadius = 45.0
strongSelf.ready.set(.single(true))
} }
self?.ready.set(.single(true))
} }
self.colorDisposable = (self.wallpaperPromise.get() self.colorDisposable = (self.wallpaperPromise.get()

View File

@ -14,17 +14,21 @@ import PresentationDataUtils
private enum ThemeSettingsColorEntryId: Hashable { private enum ThemeSettingsColorEntryId: Hashable {
case color(Int) case color(Int)
case theme(Int64)
case picker case picker
} }
private enum ThemeSettingsColorEntry: Comparable, Identifiable { private enum ThemeSettingsColorEntry: Comparable, Identifiable {
case color(Int, PresentationThemeReference, PresentationThemeAccentColor?, Bool) case color(Int, PresentationThemeReference, PresentationThemeAccentColor?, Bool)
case theme(Int, PresentationThemeReference, PresentationThemeReference, Bool)
case picker case picker
var stableId: ThemeSettingsColorEntryId { var stableId: ThemeSettingsColorEntryId {
switch self { switch self {
case let .color(index, _, _, _): case let .color(index, _, _, _):
return .color(index) return .color(index)
case let .theme(_, _, theme, _):
return .theme(theme.index)
case .picker: case .picker:
return .picker return .picker
} }
@ -38,6 +42,12 @@ private enum ThemeSettingsColorEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .theme(lhsIndex, lhsBaseThemeReference, lhsTheme, lhsSelected):
if case let .theme(rhsIndex, rhsBaseThemeReference, rhsTheme, rhsSelected) = rhs, lhsIndex == rhsIndex, lhsBaseThemeReference.index == rhsBaseThemeReference.index, lhsTheme == rhsTheme, lhsSelected == rhsSelected {
return true
} else {
return false
}
case .picker: case .picker:
if case .picker = rhs { if case .picker = rhs {
return true return true
@ -51,36 +61,109 @@ private enum ThemeSettingsColorEntry: Comparable, Identifiable {
switch lhs { switch lhs {
case .picker: case .picker:
return true return true
case let .color(lhsIndex, _, _, _): case let .color(lhsIndex, _, _, _), let .theme(lhsIndex, _, _, _):
switch rhs { switch rhs {
case let .color(rhsIndex, _, _, _): case let .color(rhsIndex, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
case let .theme(rhsIndex, _, _, _):
return lhsIndex < rhsIndex
case .picker: case .picker:
return false return false
} }
} }
} }
func item(action: @escaping (PresentationThemeAccentColor?, Bool) -> Void, contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void) -> ListViewItem { func item(action: @escaping (ThemeSettingsColorOption?, Bool) -> Void, contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void) -> ListViewItem {
switch self { switch self {
case let .color(_, themeReference, accentColor, selected): case let .color(_, themeReference, accentColor, selected):
return ThemeSettingsAccentColorIconItem(themeReference: themeReference, accentColor: accentColor, selected: selected, action: action, contextAction: contextAction) return ThemeSettingsAccentColorIconItem(themeReference: themeReference, color: accentColor.flatMap { .accentColor($0) }, selected: selected, action: action, contextAction: contextAction)
case let .theme(_, baseThemeReference, theme, selected):
return ThemeSettingsAccentColorIconItem(themeReference: baseThemeReference, color: .theme(theme), selected: selected, action: action, contextAction: contextAction)
case .picker: case .picker:
return ThemeSettingsAccentColorPickerItem(action: openColorPicker) return ThemeSettingsAccentColorPickerItem(action: openColorPicker)
} }
} }
} }
enum ThemeSettingsColorOption: Equatable {
case accentColor(PresentationThemeAccentColor)
case theme(PresentationThemeReference)
var accentColor: UIColor? {
switch self {
case let .accentColor(color):
return color.color
case let .theme(reference):
if case let .cloud(theme) = reference, let settings = theme.theme.settings {
return UIColor(rgb: UInt32(bitPattern: settings.accentColor))
} else {
return nil
}
}
}
var baseColor: UIColor? {
switch self {
case let .accentColor(color):
return color.baseColor.color
case .theme:
return nil
}
}
var plainBubbleColors: (UIColor, UIColor)? {
switch self {
case let .accentColor(color):
return color.plainBubbleColors
case let .theme(reference):
if case let .cloud(theme) = reference, let settings = theme.theme.settings, let messageColors = settings.messageColors {
return (UIColor(rgb: UInt32(bitPattern: messageColors.top)), UIColor(rgb: UInt32(bitPattern: messageColors.bottom)))
} else {
return nil
}
}
}
var customBubbleColors: (UIColor, UIColor?)? {
switch self {
case let .accentColor(color):
return color.customBubbleColors
case let .theme(reference):
if case let .cloud(theme) = reference, let settings = theme.theme.settings, let messageColors = settings.messageColors {
let topColor = UIColor(rgb: UInt32(bitPattern: messageColors.top))
let bottomColor = UIColor(rgb: UInt32(bitPattern: messageColors.bottom))
if topColor.rgb != bottomColor.rgb {
return (topColor, bottomColor)
} else {
return (topColor, nil)
}
} else {
return nil
}
}
}
var index: Int64 {
switch self {
case let .accentColor(color):
return Int64(color.index)
case let .theme(reference):
return reference.index
}
}
}
private class ThemeSettingsAccentColorIconItem: ListViewItem { private class ThemeSettingsAccentColorIconItem: ListViewItem {
let themeReference: PresentationThemeReference let themeReference: PresentationThemeReference
let accentColor: PresentationThemeAccentColor? let color: ThemeSettingsColorOption?
let selected: Bool let selected: Bool
let action: (PresentationThemeAccentColor?, Bool) -> Void let action: (ThemeSettingsColorOption?, Bool) -> Void
let contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)? let contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?
public init(themeReference: PresentationThemeReference, accentColor: PresentationThemeAccentColor?, selected: Bool, action: @escaping (PresentationThemeAccentColor?, Bool) -> Void, contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?) { public init(themeReference: PresentationThemeReference, color: ThemeSettingsColorOption?, selected: Bool, action: @escaping (ThemeSettingsColorOption?, Bool) -> Void, contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?) {
self.themeReference = themeReference self.themeReference = themeReference
self.accentColor = accentColor self.color = color
self.selected = selected self.selected = selected
self.action = action self.action = action
self.contextAction = contextAction self.contextAction = contextAction
@ -128,7 +211,7 @@ private class ThemeSettingsAccentColorIconItem: ListViewItem {
public var selectable = true public var selectable = true
public func selected(listView: ListView) { public func selected(listView: ListView) {
self.action(self.accentColor, self.selected) self.action(self.color, self.selected)
} }
} }
@ -224,7 +307,7 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
gesture.cancel() gesture.cancel()
return return
} }
item.contextAction?(item.accentColor, strongSelf.containerNode, gesture) item.contextAction?(item.color, strongSelf.containerNode, gesture)
} }
} }
@ -258,7 +341,7 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
var updatedAccentColor = false var updatedAccentColor = false
var updatedSelected = false var updatedSelected = false
if currentItem == nil || currentItem?.accentColor != item.accentColor { if currentItem == nil || currentItem?.color != item.color {
updatedAccentColor = true updatedAccentColor = true
} }
if currentItem?.selected != item.selected { if currentItem?.selected != item.selected {
@ -271,8 +354,8 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
strongSelf.item = item strongSelf.item = item
if updatedAccentColor { if updatedAccentColor {
var fillColor = item.accentColor?.color var fillColor = item.color?.accentColor
var strokeColor = item.accentColor?.baseColor.color var strokeColor = item.color?.baseColor
if strokeColor == .clear { if strokeColor == .clear {
strokeColor = fillColor strokeColor = fillColor
} }
@ -288,12 +371,12 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode {
var topColor: UIColor? var topColor: UIColor?
var bottomColor: UIColor? var bottomColor: UIColor?
if let colors = item.accentColor?.plainBubbleColors { if let colors = item.color?.plainBubbleColors {
topColor = colors.0 topColor = colors.0
bottomColor = colors.1 bottomColor = colors.1
} else if case .builtin(.dayClassic) = item.themeReference { } else if case .builtin(.dayClassic) = item.themeReference {
if let accentColor = item.accentColor { if let accentColor = item.color?.accentColor {
let hsb = accentColor.color.hsb let hsb = accentColor.hsb
let bubbleColor = UIColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0) let bubbleColor = UIColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0)
topColor = bubbleColor topColor = bubbleColor
bottomColor = bubbleColor bottomColor = bubbleColor
@ -490,15 +573,18 @@ enum ThemeSettingsAccentColor {
case color(PresentationThemeBaseColor) case color(PresentationThemeBaseColor)
case preset(PresentationThemeAccentColor) case preset(PresentationThemeAccentColor)
case custom(PresentationThemeAccentColor) case custom(PresentationThemeAccentColor)
case theme(PresentationThemeReference)
var index: Int32? { var index: Int64? {
switch self { switch self {
case .default: case .default:
return nil return nil
case let .color(color): case let .color(color):
return 10 + color.rawValue return Int64(10 + color.rawValue)
case let .preset(color), let .custom(color): case let .preset(color), let .custom(color):
return color.index return Int64(color.index)
case let .theme(theme):
return theme.index
} }
} }
} }
@ -509,13 +595,13 @@ class ThemeSettingsAccentColorItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let theme: PresentationTheme
let themeReference: PresentationThemeReference let themeReference: PresentationThemeReference
let colors: [ThemeSettingsAccentColor] let colors: [ThemeSettingsAccentColor]
let currentColor: PresentationThemeAccentColor? let currentColor: ThemeSettingsColorOption?
let updated: (PresentationThemeAccentColor?) -> Void let updated: (ThemeSettingsColorOption?) -> Void
let contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)? let contextAction: ((PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?
let openColorPicker: (Bool) -> Void let openColorPicker: (Bool) -> Void
let tag: ItemListItemTag? let tag: ItemListItemTag?
init(theme: PresentationTheme, sectionId: ItemListSectionId, themeReference: PresentationThemeReference, colors: [ThemeSettingsAccentColor], currentColor: PresentationThemeAccentColor?, updated: @escaping (PresentationThemeAccentColor?) -> Void, contextAction: ((PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, tag: ItemListItemTag? = nil) { init(theme: PresentationTheme, sectionId: ItemListSectionId, themeReference: PresentationThemeReference, colors: [ThemeSettingsAccentColor], currentColor: ThemeSettingsColorOption?, updated: @escaping (ThemeSettingsColorOption?) -> Void, contextAction: ((PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, tag: ItemListItemTag? = nil) {
self.theme = theme self.theme = theme
self.themeReference = themeReference self.themeReference = themeReference
self.colors = colors self.colors = colors
@ -568,7 +654,7 @@ private struct ThemeSettingsAccentColorItemNodeTransition {
let crossfade: Bool let crossfade: Bool
} }
private func preparedTransition(action: @escaping (PresentationThemeAccentColor?, Bool) -> Void, contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, from fromEntries: [ThemeSettingsColorEntry], to toEntries: [ThemeSettingsColorEntry], crossfade: Bool) -> ThemeSettingsAccentColorItemNodeTransition { private func preparedTransition(action: @escaping (ThemeSettingsColorOption?, Bool) -> Void, contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)?, openColorPicker: @escaping (Bool) -> Void, from fromEntries: [ThemeSettingsColorEntry], to toEntries: [ThemeSettingsColorEntry], crossfade: Bool) -> ThemeSettingsAccentColorItemNodeTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
@ -578,11 +664,11 @@ private func preparedTransition(action: @escaping (PresentationThemeAccentColor?
return ThemeSettingsAccentColorItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade) return ThemeSettingsAccentColorItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade)
} }
private func ensureColorVisible(listNode: ListView, accentColor: PresentationThemeAccentColor?, animated: Bool) -> Bool { private func ensureColorVisible(listNode: ListView, accentColor: ThemeSettingsColorOption?, animated: Bool) -> Bool {
var resultNode: ThemeSettingsAccentColorIconItemNode? var resultNode: ThemeSettingsAccentColorIconItemNode?
listNode.forEachItemNode { node in listNode.forEachItemNode { node in
if resultNode == nil, let node = node as? ThemeSettingsAccentColorIconItemNode { if resultNode == nil, let node = node as? ThemeSettingsAccentColorIconItemNode {
if node.item?.accentColor?.index == accentColor?.index { if node.item?.color?.index == accentColor?.index {
resultNode = node resultNode = node
} }
} }
@ -768,33 +854,61 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
let selected = item.currentColor == nil let selected = item.currentColor == nil
entries.append(.color(index, item.themeReference, nil, selected)) entries.append(.color(index, item.themeReference, nil, selected))
case let .color(color): case let .color(color):
let selected = item.currentColor?.baseColor == color var selected = false
let accentColor: PresentationThemeAccentColor if let currentColor = item.currentColor, case let .accentColor(accentColor) = currentColor {
selected = accentColor.baseColor == color
}
let accentColor: ThemeSettingsColorOption
if let currentColor = item.currentColor, selected { if let currentColor = item.currentColor, selected {
accentColor = currentColor accentColor = currentColor
} else { } else {
accentColor = PresentationThemeAccentColor(index: 10 + color.rawValue, baseColor: color) accentColor = .accentColor(PresentationThemeAccentColor(index: 10 + color.rawValue, baseColor: color))
}
switch accentColor {
case let .accentColor(color):
entries.append(.color(index, item.themeReference, color, selected))
case let .theme(theme):
entries.append(.theme(index, item.themeReference, theme, selected))
} }
entries.append(.color(index, item.themeReference, accentColor, selected))
case let .preset(color), let .custom(color): case let .preset(color), let .custom(color):
let selected = item.currentColor == color var selected = false
if let currentColor = item.currentColor {
selected = currentColor.index == Int64(color.index)
}
entries.append(.color(index, item.themeReference, color, selected)) entries.append(.color(index, item.themeReference, color, selected))
case let .theme(theme):
var selected = false
if let currentColor = item.currentColor {
selected = currentColor.index == theme.index
}
entries.append(.theme(index, item.themeReference, theme, selected))
} }
index += 1 index += 1
} }
let action: (PresentationThemeAccentColor?, Bool) -> Void = { [weak self] color, selected in let action: (ThemeSettingsColorOption?, Bool) -> Void = { [weak self] color, selected in
if let strongSelf = self, let item = strongSelf.item { if let strongSelf = self, let item = strongSelf.item {
if selected { if selected {
item.openColorPicker(color?.baseColor != .custom) var create = true
if let color = color {
switch color {
case let .accentColor(color):
create = color.baseColor != .custom
case let .theme(theme):
if case let .cloud(theme) = theme {
create = !theme.theme.isCreator
}
}
}
item.openColorPicker(create)
} else { } else {
item.updated(color) item.updated(color)
} }
ensureColorVisible(listNode: strongSelf.listNode, accentColor: color, animated: true) ensureColorVisible(listNode: strongSelf.listNode, accentColor: color, animated: true)
} }
} }
let contextAction: ((PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void)? = { [weak item] color, node, gesture in let contextAction: ((ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void)? = { [weak item] color, node, gesture in
if let strongSelf = self, let item = strongSelf.item { if let strongSelf = self, let item = strongSelf.item {
item.contextAction?(item.themeReference, color, node, gesture) item.contextAction?(item.themeReference, color, node, gesture)
} }

View File

@ -81,9 +81,9 @@ private final class ThemeSettingsControllerArguments {
let selectAppIcon: (String) -> Void let selectAppIcon: (String) -> Void
let editTheme: (PresentationCloudTheme) -> Void let editTheme: (PresentationCloudTheme) -> Void
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void let colorContextAction: (PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (PresentationThemeReference, PresentationThemeAccentColor?, ASDisplayNode, ContextGesture?) -> Void) { init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (String) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context self.context = context
self.selectTheme = selectTheme self.selectTheme = selectTheme
self.selectFontSize = selectFontSize self.selectFontSize = selectFontSize
@ -133,7 +133,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case fontSize(PresentationTheme, PresentationFontSize) case fontSize(PresentationTheme, PresentationFontSize)
case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem]) case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, [ChatPreviewMessageItem])
case wallpaper(PresentationTheme, String) case wallpaper(PresentationTheme, String)
case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeCustomColors?, PresentationThemeAccentColor?) case accentColor(PresentationTheme, PresentationThemeReference, PresentationThemeCustomColors?, [PresentationThemeReference], ThemeSettingsColorOption?)
case autoNightTheme(PresentationTheme, String, String) case autoNightTheme(PresentationTheme, String, String)
case textSize(PresentationTheme, String, String) case textSize(PresentationTheme, String, String)
case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?) case themeItem(PresentationTheme, PresentationStrings, [PresentationThemeReference], PresentationThemeReference, [Int64: PresentationThemeAccentColor], [Int64: TelegramWallpaper], PresentationThemeAccentColor?)
@ -208,8 +208,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .accentColor(lhsTheme, lhsCurrentTheme, lhsCustomColors, lhsColor): case let .accentColor(lhsTheme, lhsCurrentTheme, lhsCustomColors, lhsThemes, lhsColor):
if case let .accentColor(rhsTheme, rhsCurrentTheme, rhsCustomColors, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsCustomColors == rhsCustomColors, lhsColor == rhsColor { if case let .accentColor(rhsTheme, rhsCurrentTheme, rhsCustomColors, rhsThemes, rhsColor) = rhs, lhsTheme === rhsTheme, lhsCurrentTheme == rhsCurrentTheme, lhsCustomColors == rhsCustomColors, lhsThemes == rhsThemes, lhsColor == rhsColor {
return true return true
} else { } else {
return false return false
@ -308,18 +308,30 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openWallpaperSettings() arguments.openWallpaperSettings()
}) })
case let .accentColor(theme, currentTheme, customColors, color): case let .accentColor(theme, currentTheme, customColors, themes, color):
var colorItems: [ThemeSettingsAccentColor] = [] var colorItems: [ThemeSettingsAccentColor] = []
for theme in themes {
colorItems.append(.theme(theme))
}
let generalThemeReference: PresentationThemeReference
if case let .cloud(theme) = currentTheme, let settings = theme.theme.settings {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = currentTheme
}
var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue) var defaultColor: PresentationThemeAccentColor? = PresentationThemeAccentColor(baseColor: .blue)
var colors = PresentationThemeBaseColor.allCases var colors = PresentationThemeBaseColor.allCases
colors = colors.filter { $0 != .custom && $0 != .preset } colors = colors.filter { $0 != .custom && $0 != .preset }
if case let .builtin(name) = currentTheme { if case let .builtin(name) = generalThemeReference {
if name == .dayClassic { if name == .dayClassic {
colorItems.append(.default) colorItems.append(.default)
defaultColor = nil defaultColor = nil
let patternWallpaper: (String, Int32, Int32?, Int32?, Int32?) -> TelegramWallpaper = { slug, topColor, bottomColor, intensity, rotation in let patternWallpaper: (String, Int32, Int32?, Int32?, Int32?) -> TelegramWallpaper = { slug, topColor, bottomColor, intensity, rotation in
return TelegramWallpaper.file(id: 0, accessHash: 0, isCreator: false, isDefault: true, isPattern: true, isDark: false, slug: slug, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(blur: false, motion: false, color: topColor, bottomColor: bottomColor, intensity: intensity ?? 50, rotation: rotation)) return TelegramWallpaper.file(id: 0, accessHash: 0, isCreator: false, isDefault: true, isPattern: true, isDark: false, slug: slug, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(color: topColor, bottomColor: bottomColor, intensity: intensity ?? 50, rotation: rotation))
} }
colorItems.append(.preset(PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0x5a9e29, bubbleColors: (0xdcf8c6, nil), wallpaper: patternWallpaper("R3j69wKskFIBAAAAoUdXWCKMzCM", 0xede6dd, nil, 50, nil)))) colorItems.append(.preset(PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0x5a9e29, bubbleColors: (0xdcf8c6, nil), wallpaper: patternWallpaper("R3j69wKskFIBAAAAoUdXWCKMzCM", 0xede6dd, nil, 50, nil))))
@ -355,23 +367,32 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
colors = colors.filter { $0 != .white } colors = colors.filter { $0 != .white }
} }
} }
let currentColor = color ?? defaultColor let currentColor = color ?? defaultColor.flatMap { .accentColor($0) }
colorItems.append(contentsOf: colors.map { .color($0) }) colorItems.append(contentsOf: colors.map { .color($0) })
if let customColors = customColors { if let customColors = customColors {
colorItems.insert(contentsOf: customColors.colors.reversed().map { .custom($0) }, at: 0) colorItems.insert(contentsOf: customColors.colors.reversed().map { .custom($0) }, at: 0)
} else { } else {
if let currentColor = currentColor, currentColor.baseColor == .custom { // if let currentColor = currentColor, currentColor.baseColor == .custom {
colorItems.insert(.custom(currentColor), at: 0) // colorItems.insert(.custom(currentColor), at: 0)
} // }
} }
return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in return ThemeSettingsAccentColorItem(theme: theme, sectionId: self.section, themeReference: currentTheme, colors: colorItems, currentColor: currentColor, updated: { color in
arguments.selectAccentColor(color) if let color = color {
switch color {
case let .accentColor(color):
arguments.selectAccentColor(color)
case let .theme(theme):
arguments.selectTheme(theme)
}
} else {
arguments.selectAccentColor(nil)
}
}, contextAction: { theme, color, node, gesture in }, contextAction: { theme, color, node, gesture in
arguments.colorContextAction(theme, color, node, gesture) arguments.colorContextAction(theme, color, node, gesture)
}, openColorPicker: { create in }, openColorPicker: { create in
arguments.openAccentColorPicker(currentTheme, create) arguments.openAccentColorPicker(generalThemeReference, create)
}, tag: ThemeSettingsEntryTag.accentColor) }, tag: ThemeSettingsEntryTag.accentColor)
case let .autoNightTheme(theme, text, value): case let .autoNightTheme(theme, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
@ -424,10 +445,41 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
let title = presentationData.autoNightModeTriggered ? strings.Appearance_ColorThemeNight.uppercased() : strings.Appearance_ColorTheme.uppercased() let title = presentationData.autoNightModeTriggered ? strings.Appearance_ColorThemeNight.uppercased() : strings.Appearance_ColorTheme.uppercased()
entries.append(.themeListHeader(presentationData.theme, title)) entries.append(.themeListHeader(presentationData.theme, title))
entries.append(.chatPreview(presentationData.theme, presentationData.theme, presentationData.chatWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)])) entries.append(.chatPreview(presentationData.theme, presentationData.theme, presentationData.chatWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, [ChatPreviewMessageItem(outgoing: false, reply: (presentationData.strings.Appearance_PreviewReplyAuthor, presentationData.strings.Appearance_PreviewReplyText), text: presentationData.strings.Appearance_PreviewIncomingText), ChatPreviewMessageItem(outgoing: true, reply: nil, text: presentationData.strings.Appearance_PreviewOutgoingText)]))
entries.append(.themeItem(presentationData.theme, presentationData.strings, availableThemes, themeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificChatWallpapers, presentationThemeSettings.themeSpecificAccentColors[themeReference.index]))
if case let .builtin(theme) = themeReference { let generalThemes: [PresentationThemeReference] = availableThemes.filter { reference in
entries.append(.accentColor(presentationData.theme, themeReference, presentationThemeSettings.themeSpecificCustomColors[themeReference.index], presentationThemeSettings.themeSpecificAccentColors[themeReference.index])) if case let .cloud(theme) = reference {
return theme.theme.settings == nil
} else {
return true
}
}
let generalThemeReference: PresentationThemeReference
if case let .cloud(theme) = themeReference, let settings = theme.theme.settings {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = themeReference
}
entries.append(.themeItem(presentationData.theme, presentationData.strings, generalThemes, generalThemeReference, presentationThemeSettings.themeSpecificAccentColors, presentationThemeSettings.themeSpecificChatWallpapers, presentationThemeSettings.themeSpecificAccentColors[themeReference.index]))
if case let .builtin(builtinTheme) = generalThemeReference {
let colorThemes = availableThemes.filter { reference in
if case let .cloud(theme) = reference, let settings = theme.theme.settings, settings.baseTheme == builtinTheme.baseTheme {
return true
} else {
return false
}
}
var colorOption: ThemeSettingsColorOption?
if case let .builtin(theme) = themeReference {
colorOption = presentationThemeSettings.themeSpecificAccentColors[themeReference.index].flatMap { .accentColor($0) }
} else {
colorOption = .theme(themeReference)
}
entries.append(.accentColor(presentationData.theme, themeReference, presentationThemeSettings.themeSpecificCustomColors[themeReference.index], colorThemes, colorOption))
} }
entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground)) entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground))
@ -555,16 +607,23 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
if autoNightModeTriggered { if autoNightModeTriggered {
currentTheme = current.automaticThemeSwitchSetting.theme currentTheme = current.automaticThemeSwitchSetting.theme
} }
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: currentTheme, accentColor: accentColor?.color) else { let generalThemeReference: PresentationThemeReference
if case let .cloud(theme) = currentTheme, let settings = theme.theme.settings {
generalThemeReference = .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme))
} else {
generalThemeReference = currentTheme
}
guard let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.color) else {
return current return current
} }
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
var themeSpecificAccentColors = current.themeSpecificAccentColors var themeSpecificAccentColors = current.themeSpecificAccentColors
themeSpecificAccentColors[currentTheme.index] = accentColor themeSpecificAccentColors[generalThemeReference.index] = accentColor
if case let .builtin(theme) = currentTheme { if case let .builtin(theme) = generalThemeReference {
if let wallpaper = presetWallpaper, let color = accentColor { if let wallpaper = presetWallpaper, let color = accentColor {
themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: color)] = wallpaper themeSpecificChatWallpapers[coloredThemeIndex(reference: currentTheme, accentColor: color)] = wallpaper
} else if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin { } else if let wallpaper = current.themeSpecificChatWallpapers[currentTheme.index], wallpaper.isColorOrGradient || wallpaper.isPattern || wallpaper.isBuiltin {
@ -694,8 +753,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let _ = (resolvedWallpaper let _ = (resolvedWallpaper
|> deliverOnMainQueue).start(next: { wallpaper in |> deliverOnMainQueue).start(next: { wallpaper in
let controller = ThemeAccentColorController(context: context, mode: .edit(theme: theme, wallpaper: wallpaper, defaultThemeReference: nil, create: true, completion: { result in let controller = ThemeAccentColorController(context: context, mode: .edit(theme: theme, wallpaper: wallpaper, defaultThemeReference: nil, create: true, completion: { result, settings in
let controller = editThemeController(context: context, mode: .create(result), navigateToChat: { peerId in let controller = editThemeController(context: context, mode: .create(result, nil), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() { if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId))) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
} }
@ -770,14 +829,23 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
presentInGlobalOverlayImpl?(contextController, nil) presentInGlobalOverlayImpl?(contextController, nil)
}) })
}, colorContextAction: { reference, accentColor, node, gesture in }, colorContextAction: { reference, accentColor, node, gesture in
let _ = (context.sharedContext.accountManager.transaction { transaction -> (PresentationThemeAccentColor?, TelegramWallpaper?) in let _ = (context.sharedContext.accountManager.transaction { transaction -> (ThemeSettingsColorOption?, TelegramWallpaper?) in
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings) as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings
var wallpaper: TelegramWallpaper? var wallpaper: TelegramWallpaper?
if let accentColor = accentColor { if let accentColor = accentColor {
wallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: reference, accentColor: accentColor)] switch accentColor {
} case let .accentColor(accentColor):
if wallpaper == nil { wallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: reference, accentColor: accentColor)]
wallpaper = settings.themeSpecificChatWallpapers[reference.index] if wallpaper == nil {
wallpaper = settings.themeSpecificChatWallpapers[reference.index]
}
case let .theme(theme):
wallpaper = settings.themeSpecificChatWallpapers[coloredThemeIndex(reference: theme, accentColor: nil)]
}
} else {
if wallpaper == nil {
wallpaper = settings.themeSpecificChatWallpapers[reference.index]
}
} }
return (accentColor, wallpaper) return (accentColor, wallpaper)
} |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, TelegramWallpaper?), NoError> in } |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, TelegramWallpaper?), NoError> in
@ -785,12 +853,21 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
if let wallpaper = wallpaper { if let wallpaper = wallpaper {
effectiveWallpaper = wallpaper effectiveWallpaper = wallpaper
} else { } else {
let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors) let theme: PresentationTheme?
if let accentColor = accentColor, case let .theme(themeReference) = accentColor {
theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference)
} else {
theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors)
}
effectiveWallpaper = theme?.chat.defaultWallpaper ?? .builtin(WallpaperSettings()) effectiveWallpaper = theme?.chat.defaultWallpaper ?? .builtin(WallpaperSettings())
} }
return chatServiceBackgroundColor(wallpaper: effectiveWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox) return chatServiceBackgroundColor(wallpaper: effectiveWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox)
|> map { serviceBackgroundColor in |> map { serviceBackgroundColor in
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors, serviceBackgroundColor: serviceBackgroundColor), wallpaper) if let accentColor = accentColor, case let .theme(themeReference) = accentColor {
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, serviceBackgroundColor: serviceBackgroundColor), wallpaper)
} else {
return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors, serviceBackgroundColor: serviceBackgroundColor), wallpaper)
}
} }
} }
|> deliverOnMainQueue).start(next: { theme, wallpaper in |> deliverOnMainQueue).start(next: { theme, wallpaper in
@ -803,61 +880,70 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, wallpaper)) let themeController = ThemePreviewController(context: context, previewTheme: theme, source: .settings(reference, wallpaper))
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
if let accentColor = accentColor, accentColor.baseColor == .custom { if let accentColor = accentColor {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveThemeColor, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) if case let .accentColor(color) = accentColor, color.baseColor != .custom {
}, action: { c, f in } else {
c.dismiss(completion: { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareThemeColor, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor)
let actionSheet = ActionSheetController(presentationData: presentationData) }, action: { c, f in
var items: [ActionSheetItem] = [] c.dismiss(completion: {
items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeColorConfirmation, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in })
let themeReference: PresentationThemeReference })))
if presentationData.autoNightModeTriggered { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveThemeColor, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
themeReference = current.automaticThemeSwitchSetting.theme }, action: { c, f in
} else { c.dismiss(completion: {
themeReference = current.theme let actionSheet = ActionSheetController(presentationData: presentationData)
} var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeColorConfirmation, color: .destructive, action: { [weak actionSheet] in
var themeSpecificAccentColors = current.themeSpecificAccentColors
var themeSpecificCustomColors = current.themeSpecificCustomColors
var customColors = themeSpecificCustomColors[themeReference.index]?.colors ?? []
var updatedAccentColor: PresentationThemeAccentColor?
if let index = customColors.firstIndex(where: { $0.index == accentColor.index }) {
if index > 0 {
updatedAccentColor = customColors[index - 1]
} else {
if case let .builtin(theme) = themeReference {
switch theme {
case .dayClassic:
updatedAccentColor = nil
case .day, .night, .nightAccent:
updatedAccentColor = PresentationThemeAccentColor(baseColor: .blue)
}
} else {
updatedAccentColor = PresentationThemeAccentColor(baseColor: .blue)
}
}
customColors.remove(at: index)
} else {
updatedAccentColor = PresentationThemeAccentColor(baseColor: .blue)
}
themeSpecificAccentColors[themeReference.index] = updatedAccentColor
themeSpecificCustomColors[themeReference.index] = PresentationThemeCustomColors(colors: customColors)
return current.withUpdatedThemeSpecificCustomColors(themeSpecificCustomColors).withUpdatedThemeSpecificAccentColors(themeSpecificAccentColors)
}).start()
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
})
])]) let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
presentControllerImpl?(actionSheet, nil) let themeReference: PresentationThemeReference
}) if presentationData.autoNightModeTriggered {
}))) themeReference = current.automaticThemeSwitchSetting.theme
} else {
themeReference = current.theme
}
var themeSpecificAccentColors = current.themeSpecificAccentColors
var themeSpecificCustomColors = current.themeSpecificCustomColors
var customColors = themeSpecificCustomColors[themeReference.index]?.colors ?? []
var updatedAccentColor: PresentationThemeAccentColor?
if let index = customColors.firstIndex(where: { $0.index == accentColor.index }) {
if index > 0 {
updatedAccentColor = customColors[index - 1]
} else {
if case let .builtin(theme) = themeReference {
switch theme {
case .dayClassic:
updatedAccentColor = nil
case .day, .night, .nightAccent:
updatedAccentColor = PresentationThemeAccentColor(baseColor: .blue)
}
} else {
updatedAccentColor = PresentationThemeAccentColor(baseColor: .blue)
}
}
customColors.remove(at: index)
} else {
updatedAccentColor = PresentationThemeAccentColor(baseColor: .blue)
}
themeSpecificAccentColors[themeReference.index] = updatedAccentColor
themeSpecificCustomColors[themeReference.index] = PresentationThemeCustomColors(colors: customColors)
return current.withUpdatedThemeSpecificCustomColors(themeSpecificCustomColors).withUpdatedThemeSpecificAccentColors(themeSpecificAccentColors)
}).start()
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
presentControllerImpl?(actionSheet, nil)
})
})))
}
} }
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil) presentInGlobalOverlayImpl?(contextController, nil)
@ -933,21 +1019,32 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
if let controller = controller, controller.isNodeLoaded, let navigationController = controller.navigationController as? NavigationController, navigationController.topViewController === controller { if let controller = controller, controller.isNodeLoaded, let navigationController = controller.navigationController as? NavigationController, navigationController.topViewController === controller {
var topOffset: CGFloat? var topOffset: CGFloat?
var bottomOffset: CGFloat? var bottomOffset: CGFloat?
var leftOffset: CGFloat?
var themeItemNode: ThemeSettingsThemeItemNode? var themeItemNode: ThemeSettingsThemeItemNode?
var colorItemNode: ThemeSettingsAccentColorItemNode? var colorItemNode: ThemeSettingsAccentColorItemNode?
var view = controller.navigationController?.view
let controllerFrame = controller.view.convert(controller.view.bounds, to: controller.navigationController?.view)
if controllerFrame.minX > 0.0 {
leftOffset = controllerFrame.minX
}
if controllerFrame.minY > 100.0 {
view = nil
}
controller.forEachItemNode { node in controller.forEachItemNode { node in
if let itemNode = node as? ItemListItemNode { if let itemNode = node as? ItemListItemNode {
if let itemTag = itemNode.tag { if let itemTag = itemNode.tag {
if itemTag.isEqual(to: ThemeSettingsEntryTag.theme) { if itemTag.isEqual(to: ThemeSettingsEntryTag.theme) {
let frame = node.convert(node.bounds, to: controller.displayNode) let frame = node.view.convert(node.view.bounds, to: controller.navigationController?.view)
topOffset = frame.minY topOffset = frame.minY
bottomOffset = frame.maxY bottomOffset = frame.maxY
if let itemNode = node as? ThemeSettingsThemeItemNode { if let itemNode = node as? ThemeSettingsThemeItemNode {
themeItemNode = itemNode themeItemNode = itemNode
} }
} else if itemTag.isEqual(to: ThemeSettingsEntryTag.accentColor) && hasAccentColors { } else if itemTag.isEqual(to: ThemeSettingsEntryTag.accentColor) && hasAccentColors {
let frame = node.convert(node.bounds, to: controller.displayNode) let frame = node.view.convert(node.view.bounds, to: controller.navigationController?.view)
bottomOffset = frame.maxY bottomOffset = frame.maxY
if let itemNode = node as? ThemeSettingsAccentColorItemNode { if let itemNode = node as? ThemeSettingsAccentColorItemNode {
colorItemNode = itemNode colorItemNode = itemNode
@ -965,13 +1062,17 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
} }
} }
themeItemNode?.prepareCrossfadeTransition() if view != nil {
colorItemNode?.prepareCrossfadeTransition() themeItemNode?.prepareCrossfadeTransition()
colorItemNode?.prepareCrossfadeTransition()
}
let crossfadeController = ThemeSettingsCrossfadeController(view: controller.view, topOffset: topOffset, bottomOffset: bottomOffset) let crossfadeController = ThemeSettingsCrossfadeController(view: view, topOffset: topOffset, bottomOffset: bottomOffset, leftOffset: leftOffset)
crossfadeController.didAppear = { [weak themeItemNode, weak colorItemNode] in crossfadeController.didAppear = { [weak themeItemNode, weak colorItemNode] in
themeItemNode?.animateCrossfadeTransition() if view != nil {
colorItemNode?.animateCrossfadeTransition() themeItemNode?.animateCrossfadeTransition()
colorItemNode?.animateCrossfadeTransition()
}
} }
context.sharedContext.presentGlobalController(crossfadeController, nil) context.sharedContext.presentGlobalController(crossfadeController, nil)
@ -1044,8 +1145,8 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
return themeReference return themeReference
} }
|> deliverOnMainQueue).start(next: { themeReference in |> deliverOnMainQueue).start(next: { themeReference in
let controller = ThemeAccentColorController(context: context, mode: .edit(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper, defaultThemeReference: themeReference, create: true, completion: { result in let controller = ThemeAccentColorController(context: context, mode: .edit(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper, defaultThemeReference: themeReference, create: true, completion: { result, settings in
let controller = editThemeController(context: context, mode: .create(result), navigateToChat: { peerId in let controller = editThemeController(context: context, mode: .create(result, settings), navigateToChat: { peerId in
if let navigationController = getNavigationControllerImpl?() { if let navigationController = getNavigationControllerImpl?() {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId))) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
} }
@ -1063,7 +1164,6 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}) })
})) }))
pushControllerImpl?(controller) pushControllerImpl?(controller)
}) })
})) }))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
@ -1081,11 +1181,41 @@ public final class ThemeSettingsCrossfadeController: ViewController {
private var topSnapshotView: UIView? private var topSnapshotView: UIView?
private var bottomSnapshotView: UIView? private var bottomSnapshotView: UIView?
private var sideSnapshotView: UIView?
fileprivate var didAppear: (() -> Void)? fileprivate var didAppear: (() -> Void)?
public init(view: UIView? = nil, topOffset: CGFloat? = nil, bottomOffset: CGFloat? = nil) { public init(view: UIView? = nil, topOffset: CGFloat? = nil, bottomOffset: CGFloat? = nil, leftOffset: CGFloat? = nil) {
if let view = view { if let view = view {
if var leftOffset = leftOffset {
leftOffset += UIScreenPixel
if let view = view.snapshotView(afterScreenUpdates: false) {
let clipView = UIView()
clipView.clipsToBounds = true
clipView.addSubview(view)
view.clipsToBounds = true
view.contentMode = .topLeft
if let topOffset = topOffset, let bottomOffset = bottomOffset {
var frame = view.frame
frame.origin.y = topOffset
frame.size.width = leftOffset
frame.size.height = bottomOffset - topOffset
clipView.frame = frame
frame = view.frame
frame.origin.y = -topOffset
frame.size.width = leftOffset
frame.size.height = bottomOffset
view.frame = frame
}
self.sideSnapshotView = clipView
}
}
if let view = view.snapshotView(afterScreenUpdates: false) { if let view = view.snapshotView(afterScreenUpdates: false) {
view.clipsToBounds = true view.clipsToBounds = true
view.contentMode = .top view.contentMode = .top
@ -1135,6 +1265,9 @@ public final class ThemeSettingsCrossfadeController: ViewController {
if let bottomSnapshotView = self.bottomSnapshotView { if let bottomSnapshotView = self.bottomSnapshotView {
self.displayNode.view.addSubview(bottomSnapshotView) self.displayNode.view.addSubview(bottomSnapshotView)
} }
if let sideSnapshotView = self.sideSnapshotView {
self.displayNode.view.addSubview(sideSnapshotView)
}
} }
override public func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {

View File

@ -24,8 +24,8 @@ public enum WallpaperListType {
public enum WallpaperListSource { public enum WallpaperListSource {
case list(wallpapers: [TelegramWallpaper], central: TelegramWallpaper, type: WallpaperListType) case list(wallpapers: [TelegramWallpaper], central: TelegramWallpaper, type: WallpaperListType)
case wallpaper(TelegramWallpaper, WallpaperPresentationOptions?, UIColor?, Int32?, Message?) case wallpaper(TelegramWallpaper, WallpaperPresentationOptions?, UIColor?, UIColor?, Int32?, Int32?, Message?)
case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?, UIColor?, Int32?, Message?) case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?, UIColor?, UIColor?, Int32?, Int32?, Message?)
case asset(PHAsset) case asset(PHAsset)
case contextResult(ChatContextResult) case contextResult(ChatContextResult)
case customColor(Int32?) case customColor(Int32?)
@ -97,27 +97,31 @@ class WallpaperGalleryControllerNode: GalleryControllerNode {
} }
} }
private func updatedFileWallpaper(wallpaper: TelegramWallpaper, color: UIColor?, intensity: Int32?) -> TelegramWallpaper { private func updatedFileWallpaper(wallpaper: TelegramWallpaper, firstColor: UIColor?, secondColor: UIColor?, intensity: Int32?, rotation: Int32?) -> TelegramWallpaper {
if case let .file(file) = wallpaper { if case let .file(file) = wallpaper {
return updatedFileWallpaper(id: file.id, accessHash: file.accessHash, slug: file.slug, file: file.file, color: color, intensity: intensity) return updatedFileWallpaper(id: file.id, accessHash: file.accessHash, slug: file.slug, file: file.file, firstColor: firstColor, secondColor: secondColor, intensity: intensity, rotation: rotation)
} else { } else {
return wallpaper return wallpaper
} }
} }
private func updatedFileWallpaper(id: Int64? = nil, accessHash: Int64? = nil, slug: String, file: TelegramMediaFile, color: UIColor?, intensity: Int32?) -> TelegramWallpaper { private func updatedFileWallpaper(id: Int64? = nil, accessHash: Int64? = nil, slug: String, file: TelegramMediaFile, firstColor: UIColor?, secondColor: UIColor?, intensity: Int32?, rotation: Int32?) -> TelegramWallpaper {
let isPattern = file.mimeType == "image/png" let isPattern = file.mimeType == "image/png"
var colorValue: Int32? var firstColorValue: Int32?
var secondColorValue: Int32?
var intensityValue: Int32? var intensityValue: Int32?
if let color = color { if let firstColor = firstColor {
colorValue = Int32(bitPattern: color.rgb) firstColorValue = Int32(bitPattern: firstColor.rgb)
intensityValue = intensity intensityValue = intensity
} else if isPattern { } else if isPattern {
colorValue = 0xd6e2ee firstColorValue = 0xd6e2ee
intensityValue = 50 intensityValue = 50
} }
if let secondColor = secondColor {
secondColorValue = Int32(bitPattern: secondColor.rgb)
}
return .file(id: id ?? 0, accessHash: accessHash ?? 0, isCreator: false, isDefault: false, isPattern: isPattern, isDark: false, slug: slug, file: file, settings: WallpaperSettings(blur: false, motion: false, color: colorValue, intensity: intensityValue)) return .file(id: id ?? 0, accessHash: accessHash ?? 0, isCreator: false, isDefault: false, isPattern: isPattern, isDark: false, slug: slug, file: file, settings: WallpaperSettings(color: firstColorValue, bottomColor: secondColorValue, intensity: intensityValue, rotation: rotation))
} }
public class WallpaperGalleryController: ViewController { public class WallpaperGalleryController: ViewController {
@ -183,15 +187,15 @@ public class WallpaperGalleryController: ViewController {
if case let .wallpapers(wallpaperOptions) = type, let options = wallpaperOptions { if case let .wallpapers(wallpaperOptions) = type, let options = wallpaperOptions {
self.initialOptions = options self.initialOptions = options
} }
case let .slug(slug, file, options, color, intensity, message): case let .slug(slug, file, options, firstColor, secondColor, intensity, rotation, message):
if let file = file { if let file = file {
let wallpaper = updatedFileWallpaper(slug: slug, file: file, color: color, intensity: intensity) let wallpaper = updatedFileWallpaper(slug: slug, file: file, firstColor: firstColor, secondColor: secondColor, intensity: intensity, rotation: rotation)
entries = [.wallpaper(wallpaper, message)] entries = [.wallpaper(wallpaper, message)]
centralEntryIndex = 0 centralEntryIndex = 0
self.initialOptions = options self.initialOptions = options
} }
case let .wallpaper(wallpaper, options, color, intensity, message): case let .wallpaper(wallpaper, options, firstColor, secondColor, intensity, rotation, message):
let wallpaper = updatedFileWallpaper(wallpaper: wallpaper, color: color, intensity: intensity) let wallpaper = updatedFileWallpaper(wallpaper: wallpaper, firstColor: firstColor, secondColor: secondColor, intensity: intensity, rotation: rotation)
entries = [.wallpaper(wallpaper, message)] entries = [.wallpaper(wallpaper, message)]
centralEntryIndex = 0 centralEntryIndex = 0
self.initialOptions = options self.initialOptions = options
@ -776,11 +780,18 @@ public class WallpaperGalleryController: ViewController {
case let .file(_, _, _, _, isPattern, _, slug, _, settings): case let .file(_, _, _, _, isPattern, _, slug, _, settings):
if isPattern { if isPattern {
if let color = settings.color { if let color = settings.color {
options.append("bg_color=\(UIColor(rgb: UInt32(bitPattern: color)).hexString)") if let bottomColor = settings.bottomColor {
options.append("bg_color=\(UIColor(rgb: UInt32(bitPattern: color)).hexString)-\(UIColor(rgb: UInt32(bitPattern: bottomColor)).hexString)")
} else {
options.append("bg_color=\(UIColor(rgb: UInt32(bitPattern: color)).hexString)")
}
} }
if let intensity = settings.intensity { if let intensity = settings.intensity {
options.append("intensity=\(intensity)") options.append("intensity=\(intensity)")
} }
if let rotation = settings.rotation {
options.append("rotation=\(rotation)")
}
} }
var optionsString = "" var optionsString = ""

View File

@ -233,10 +233,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
actionSignal = .single(defaultAction) actionSignal = .single(defaultAction)
colorSignal = chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: self.context.account.postbox.mediaBox) colorSignal = chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: self.context.account.postbox.mediaBox)
isBlurrable = false isBlurrable = false
case let .gradient(topColor, bottomColor, _): case let .gradient(topColor, bottomColor, settings):
displaySize = CGSize(width: 1.0, height: 1.0) displaySize = CGSize(width: 1.0, height: 1.0)
contentSize = displaySize contentSize = displaySize
signal = gradientImage([UIColor(rgb: UInt32(bitPattern: topColor)), UIColor(rgb: UInt32(bitPattern: bottomColor))]) signal = gradientImage([UIColor(rgb: UInt32(bitPattern: topColor)), UIColor(rgb: UInt32(bitPattern: bottomColor))], rotation: settings.rotation)
fetchSignal = .complete() fetchSignal = .complete()
statusSignal = .single(.Local) statusSignal = .single(.Local)
subtitleSignal = .single(nil) subtitleSignal = .single(nil)

View File

@ -150,7 +150,7 @@ final class WallpaperPatternPanelNode: ASDisplayNode {
var updatedWallpaper = wallpaper var updatedWallpaper = wallpaper
if case let .file(file) = updatedWallpaper { if case let .file(file) = updatedWallpaper {
let settings = WallpaperSettings(blur: false, motion: false, color: Int32(bitPattern: backgroundColors.0.rgb), bottomColor: backgroundColors.1.flatMap { Int32(bitPattern: $0.rgb) }, intensity: 100) let settings = WallpaperSettings(color: Int32(bitPattern: backgroundColors.0.rgb), bottomColor: backgroundColors.1.flatMap { Int32(bitPattern: $0.rgb) }, intensity: 100)
updatedWallpaper = .file(id: file.id, accessHash: file.accessHash, isCreator: file.isCreator, isDefault: file.isDefault, isPattern: file.isPattern, isDark: file.isDark, slug: file.slug, file: file.file, settings: settings) updatedWallpaper = .file(id: file.id, accessHash: file.accessHash, isCreator: file.isCreator, isDefault: file.isDefault, isPattern: file.isPattern, isDark: file.isDark, slug: file.slug, file: file.file, settings: settings)
} }

View File

@ -1727,7 +1727,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
}, displaySwipeToReplyHint: { [weak self] in }, displaySwipeToReplyHint: { [weak self] in
if let strongSelf = self, let validLayout = strongSelf.validLayout, min(validLayout.size.width, validLayout.size.height) > 320.0 { if let strongSelf = self, let validLayout = strongSelf.validLayout, min(validLayout.size.width, validLayout.size.height) > 320.0 {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .swipeToReply(title: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintTitle, text: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintText), elevatedLayout: true, action: { _ in }), in: .window(.root)) strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .swipeToReply(title: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintTitle, text: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintText), elevatedLayout: true, action: { _ in return false }), in: .window(.root))
} }
}, dismissReplyMarkupMessage: { [weak self] message in }, dismissReplyMarkupMessage: { [weak self] message in
guard let strongSelf = self, strongSelf.presentationInterfaceState.keyboardButtonsMessage?.id == message.id else { guard let strongSelf = self, strongSelf.presentationInterfaceState.keyboardButtonsMessage?.id == message.id else {
@ -5079,14 +5079,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
statusText = strongSelf.presentationData.strings.Undo_ChatCleared statusText = strongSelf.presentationData.strings.Undo_ChatCleared
} }
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: true, action: { shouldCommit in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: true, action: { value in
if shouldCommit { if value == .commit {
let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: { let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: {
self?.chatDisplayNode.historyNode.historyAppearsCleared = false self?.chatDisplayNode.historyNode.historyAppearsCleared = false
}) })
} else { return true
} else if value == .undo {
self?.chatDisplayNode.historyNode.historyAppearsCleared = false self?.chatDisplayNode.historyNode.historyAppearsCleared = false
return true
} }
return false
}), in: .current) }), in: .current)
} }
@ -5349,7 +5352,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
disposable.set((signal disposable.set((signal
|> deliverOnMainQueue).start(completed: { [weak self] in |> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self, let layout = strongSelf.validLayout { if let strongSelf = self, let layout = strongSelf.validLayout {
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: true, action: { _ in }), in: .current) strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: true, action: { _ in return false }), in: .current)
} }
})) }))

View File

@ -247,11 +247,15 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
mediaAndFlags = (webpage.image ?? file, [.preferMediaBeforeText]) mediaAndFlags = (webpage.image ?? file, [.preferMediaBeforeText])
} }
} else if webpage.type == "telegram_background" { } else if webpage.type == "telegram_background" {
var patternColor: UIColor? var topColor: UIColor?
if let wallpaper = parseWallpaperUrl(webpage.url), case let .slug(_, _, color, intensity) = wallpaper { var bottomColor: UIColor?
patternColor = color?.withAlphaComponent(CGFloat(intensity ?? 50) / 100.0) var rotation: Int32?
if let wallpaper = parseWallpaperUrl(webpage.url), case let .slug(_, _, firstColor, secondColor, intensity, rotationValue) = wallpaper {
topColor = firstColor?.withAlphaComponent(CGFloat(intensity ?? 50) / 100.0)
bottomColor = secondColor?.withAlphaComponent(CGFloat(intensity ?? 50) / 100.0)
rotation = rotationValue
} }
let media = WallpaperPreviewMedia(content: .file(file, patternColor, nil, 0, false, false)) let media = WallpaperPreviewMedia(content: .file(file, topColor, bottomColor, rotation, false, false))
mediaAndFlags = (media, [.preferMediaAspectFilled]) mediaAndFlags = (media, [.preferMediaAspectFilled])
if let fileSize = file.size { if let fileSize = file.size {
badge = dataSizeString(fileSize, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) badge = dataSizeString(fileSize, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
@ -279,23 +283,31 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
} }
} else if let type = webpage.type { } else if let type = webpage.type {
if type == "telegram_background" { if type == "telegram_background" {
if let text = webpage.text { var topColor: UIColor?
let colorCodeRange = text.range(of: "#") var bottomColor: UIColor?
if colorCodeRange != nil { var rotation: Int32?
let components = text.replacingOccurrences(of: "#", with: "").components(separatedBy: "-") if let wallpaper = parseWallpaperUrl(webpage.url) {
if components.count == 2, let topColorCode = components.first, let bottomColorCode = components.last { if case let .color(color) = wallpaper {
if let topColor = UIColor(hexString: topColorCode), let bottomColor = UIColor(hexString: bottomColorCode) { topColor = color
let media = WallpaperPreviewMedia(content: .gradient(topColor, bottomColor, 0)) } else if case let .gradient(topColorValue, bottomColorValue, rotationValue) = wallpaper {
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags()) topColor = topColorValue
} bottomColor = bottomColorValue
} else if components.count == 1, let colorCode = components.first { rotation = rotationValue
if let color = UIColor(hexString: colorCode) {
let media = WallpaperPreviewMedia(content: .color(color))
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
}
}
} }
} }
var content: WallpaperPreviewMediaContent?
if let topColor = topColor {
if let bottomColor = bottomColor {
content = .gradient(topColor, bottomColor, rotation)
} else {
content = .color(topColor)
}
}
if let content = content {
let media = WallpaperPreviewMedia(content: content)
mediaAndFlags = (media, [])
}
} else if type == "telegram_theme" { } else if type == "telegram_theme" {
var file: TelegramMediaFile? var file: TelegramMediaFile?
var isSupported = false var isSupported = false

View File

@ -486,12 +486,12 @@ func openChatWallpaper(context: AccountContext, message: Message, present: @esca
if case let .wallpaper(parameter) = resolvedUrl { if case let .wallpaper(parameter) = resolvedUrl {
let source: WallpaperListSource let source: WallpaperListSource
switch parameter { switch parameter {
case let .slug(slug, options, color, intensity): case let .slug(slug, options, firstColor, secondColor, intensity, rotation):
source = .slug(slug, content.file, options, color, intensity, message) source = .slug(slug, content.file, options, firstColor, secondColor, intensity, rotation, message)
case let .color(color): case let .color(color):
source = .wallpaper(.color(Int32(color.rgb)), nil, nil, nil, message) source = .wallpaper(.color(Int32(color.rgb)), nil, nil, nil, nil, nil, message)
case let .gradient(topColor, bottomColor): case let .gradient(topColor, bottomColor, rotation):
source = .wallpaper(.gradient(Int32(topColor.rgb), Int32(bottomColor.rgb), WallpaperSettings()), nil, nil, nil, message) source = .wallpaper(.gradient(Int32(topColor.rgb), Int32(bottomColor.rgb), WallpaperSettings(rotation: rotation)), nil, nil, nil, nil, rotation, message)
} }
let controller = WallpaperGalleryController(context: context, source: source) let controller = WallpaperGalleryController(context: context, source: source)

View File

@ -249,26 +249,30 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
let signal: Signal<TelegramWallpaper, GetWallpaperError> let signal: Signal<TelegramWallpaper, GetWallpaperError>
var options: WallpaperPresentationOptions? var options: WallpaperPresentationOptions?
var color: UIColor? var topColor: UIColor?
var bottomColor: UIColor?
var intensity: Int32? var intensity: Int32?
var rotation: Int32?
switch parameter { switch parameter {
case let .slug(slug, wallpaperOptions, patternColor, patternIntensity): case let .slug(slug, wallpaperOptions, firstColor, secondColor, intensityValue, rotationValue):
signal = getWallpaper(account: context.account, slug: slug) signal = getWallpaper(account: context.account, slug: slug)
options = wallpaperOptions options = wallpaperOptions
color = patternColor topColor = firstColor
intensity = patternIntensity bottomColor = secondColor
intensity = intensityValue
rotation = rotationValue
controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
present(controller!, nil) present(controller!, nil)
case let .color(color): case let .color(color):
signal = .single(.color(Int32(color.rgb))) signal = .single(.color(Int32(color.rgb)))
case let .gradient(topColor, bottomColor): case let .gradient(topColor, bottomColor, rotation):
signal = .single(.gradient(Int32(topColor.rgb), Int32(bottomColor.rgb), WallpaperSettings())) signal = .single(.gradient(Int32(topColor.rgb), Int32(bottomColor.rgb), WallpaperSettings()))
} }
let _ = (signal let _ = (signal
|> deliverOnMainQueue).start(next: { [weak controller] wallpaper in |> deliverOnMainQueue).start(next: { [weak controller] wallpaper in
controller?.dismiss() controller?.dismiss()
let galleryController = WallpaperGalleryController(context: context, source: .wallpaper(wallpaper, options, color, intensity, nil)) let galleryController = WallpaperGalleryController(context: context, source: .wallpaper(wallpaper, options, topColor, bottomColor, intensity, rotation, nil))
present(galleryController, nil) present(galleryController, nil)
}, error: { [weak controller] error in }, error: { [weak controller] error in
controller?.dismiss() controller?.dismiss()
@ -316,8 +320,6 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
return disposables return disposables
} }
} }
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
present(controller, nil)
var cancelImpl: (() -> Void)? var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in let progressSignal = Signal<Never, NoError> { subscriber in
@ -345,14 +347,12 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
progressDisposable.dispose() progressDisposable.dispose()
} }
} }
|> deliverOnMainQueue).start(next: { [weak controller] dataAndTheme in |> deliverOnMainQueue).start(next: { dataAndTheme in
controller?.dismiss()
if let theme = makePresentationTheme(data: dataAndTheme.0) { if let theme = makePresentationTheme(data: dataAndTheme.0) {
let previewController = ThemePreviewController(context: context, previewTheme: theme, source: .theme(dataAndTheme.1)) let previewController = ThemePreviewController(context: context, previewTheme: theme, source: .theme(dataAndTheme.1))
navigationController?.pushViewController(previewController) navigationController?.pushViewController(previewController)
} }
}, error: { [weak controller] error in }, error: { error in
let errorText: String let errorText: String
switch error { switch error {
case .generic, .slugInvalid: case .generic, .slugInvalid:
@ -361,7 +361,6 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
errorText = presentationData.strings.Theme_Unsupported errorText = presentationData.strings.Theme_Unsupported
} }
present(textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) present(textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
controller?.dismiss()
})) }))
dismissInput() dismissInput()
case let .wallet(address, amount, comment): case let .wallet(address, amount, comment):

View File

@ -546,7 +546,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
} else if parsedUrl.host == "bg" { } else if parsedUrl.host == "bg" {
if let components = URLComponents(string: "/?" + query) { if let components = URLComponents(string: "/?" + query) {
var parameter: String? var parameter: String?
var mode = "" var query: [String] = []
if let queryItems = components.queryItems { if let queryItems = components.queryItems {
for queryItem in queryItems { for queryItem in queryItems {
if let value = queryItem.value { if let value = queryItem.value {
@ -557,13 +557,23 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
} else if queryItem.name == "gradient" { } else if queryItem.name == "gradient" {
parameter = value parameter = value
} else if queryItem.name == "mode" { } else if queryItem.name == "mode" {
mode = "?mode=\(value)" query.append("mode=\(value)")
} else if queryItem.name == "bg_color" {
query.append("bg_color=\(value)")
} else if queryItem.name == "intensity" {
query.append("intensity=\(value)")
} else if queryItem.name == "rotation" {
query.append("rotation=\(value)")
} }
} }
} }
} }
var queryString = ""
if !query.isEmpty {
queryString = "?\(query.joined(separator: "&"))"
}
if let parameter = parameter { if let parameter = parameter {
convertedUrl = "https://t.me/bg/\(parameter)\(mode)" convertedUrl = "https://t.me/bg/\(parameter)\(queryString)"
} }
} }
} else if parsedUrl.host == "addtheme" { } else if parsedUrl.host == "addtheme" {

View File

@ -10,6 +10,32 @@ public enum PresentationBuiltinThemeReference: Int32 {
case night = 1 case night = 1
case day = 2 case day = 2
case nightAccent = 3 case nightAccent = 3
public init(baseTheme: TelegramBaseTheme) {
switch baseTheme {
case .classic:
self = .dayClassic
case .day:
self = .day
case .night:
self = .night
case .tinted:
self = .nightAccent
}
}
public var baseTheme: TelegramBaseTheme {
switch self {
case .dayClassic:
return .classic
case .day:
return .day
case .night:
return .night
case .nightAccent:
return .tinted
}
}
} }
public struct WallpaperPresentationOptions: OptionSet { public struct WallpaperPresentationOptions: OptionSet {

View File

@ -14,17 +14,23 @@ public enum UndoOverlayContent {
case actionSucceeded(title: String, text: String, cancel: String) case actionSucceeded(title: String, text: String, cancel: String)
} }
public enum UndoOverlayAction {
case info
case undo
case commit
}
public final class UndoOverlayController: ViewController { public final class UndoOverlayController: ViewController {
private let presentationData: PresentationData private let presentationData: PresentationData
public let content: UndoOverlayContent public let content: UndoOverlayContent
private let elevatedLayout: Bool private let elevatedLayout: Bool
private let animateInAsReplacement: Bool private let animateInAsReplacement: Bool
private var action: (Bool) -> Void private var action: (UndoOverlayAction) -> Bool
private var didPlayPresentationAnimation = false private var didPlayPresentationAnimation = false
private var dismissed = false private var dismissed = false
public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, animateInAsReplacement: Bool = false, action: @escaping (Bool) -> Void) { public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, animateInAsReplacement: Bool = false, action: @escaping (UndoOverlayAction) -> Bool) {
self.presentationData = presentationData self.presentationData = presentationData
self.content = content self.content = content
self.elevatedLayout = elevatedLayout self.elevatedLayout = elevatedLayout
@ -42,7 +48,7 @@ public final class UndoOverlayController: ViewController {
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, action: { [weak self] value in self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, action: { [weak self] value in
self?.action(value) return self?.action(value) ?? false
}, dismiss: { [weak self] in }, dismiss: { [weak self] in
self?.dismiss() self?.dismiss()
}) })
@ -50,12 +56,12 @@ public final class UndoOverlayController: ViewController {
} }
public func dismissWithCommitAction() { public func dismissWithCommitAction() {
self.action(true) self.action(.commit)
self.dismiss() self.dismiss()
} }
public func dismissWithCommitActionAndReplacementAnimation() { public func dismissWithCommitActionAndReplacementAnimation() {
self.action(true) self.action(.commit)
(self.displayNode as! UndoOverlayControllerNode).animateOutWithReplacement(completion: { [weak self] in (self.displayNode as! UndoOverlayControllerNode).animateOutWithReplacement(completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil) self?.presentingViewController?.dismiss(animated: false, completion: nil)
}) })

View File

@ -21,11 +21,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let animatedStickerNode: AnimatedStickerNode? private let animatedStickerNode: AnimatedStickerNode?
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private let buttonTextNode: ImmediateTextNode
private let buttonNode: HighlightTrackingButtonNode private let buttonNode: HighlightTrackingButtonNode
private let undoButtonTextNode: ImmediateTextNode
private let undoButtonNode: HighlightTrackingButtonNode
private let panelNode: ASDisplayNode private let panelNode: ASDisplayNode
private let panelWrapperNode: ASDisplayNode private let panelWrapperNode: ASDisplayNode
private let action: (Bool) -> Void private let action: (UndoOverlayAction) -> Bool
private let dismiss: () -> Void private let dismiss: () -> Void
private let effectView: UIView private let effectView: UIView
@ -38,7 +39,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private var validLayout: ContainerViewLayout? private var validLayout: ContainerViewLayout?
init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, action: @escaping (Bool) -> Void, dismiss: @escaping () -> Void) { init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
self.elevatedLayout = elevatedLayout self.elevatedLayout = elevatedLayout
self.action = action self.action = action
@ -55,6 +56,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.displaysAsynchronously = false self.textNode.displaysAsynchronously = false
self.textNode.maximumNumberOfLines = 0 self.textNode.maximumNumberOfLines = 0
self.buttonNode = HighlightTrackingButtonNode()
var displayUndo = true var displayUndo = true
var undoText = presentationData.strings.Undo_Undo var undoText = presentationData.strings.Undo_Undo
var undoTextColor = UIColor(rgb: 0x5ac8fa) var undoTextColor = UIColor(rgb: 0x5ac8fa)
@ -129,12 +132,17 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.iconCheckNode = nil self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0) self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil self.animatedStickerNode = nil
undoTextColor = UIColor(rgb: 0xff7b74)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) self.textNode.attributedText = attributedText
displayUndo = true displayUndo = true
undoText = cancel undoText = cancel
undoTextColor = UIColor(rgb: 0xff7b74)
self.originalRemainingSeconds = 3 self.originalRemainingSeconds = 3
case let .emoji(path, text): case let .emoji(path, text):
self.iconNode = nil self.iconNode = nil
@ -167,11 +175,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear) self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
self.buttonTextNode = ImmediateTextNode() self.undoButtonTextNode = ImmediateTextNode()
self.buttonTextNode.displaysAsynchronously = false self.undoButtonTextNode.displaysAsynchronously = false
self.buttonTextNode.attributedText = NSAttributedString(string: undoText, font: Font.regular(17.0), textColor: undoTextColor) self.undoButtonTextNode.attributedText = NSAttributedString(string: undoText, font: Font.regular(17.0), textColor: undoTextColor)
self.buttonNode = HighlightTrackingButtonNode() self.undoButtonNode = HighlightTrackingButtonNode()
self.panelNode = ASDisplayNode() self.panelNode = ASDisplayNode()
if presentationData.theme.overallDarkAppearance { if presentationData.theme.overallDarkAppearance {
@ -201,25 +209,27 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode) self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode)
self.panelWrapperNode.addSubnode(self.titleNode) self.panelWrapperNode.addSubnode(self.titleNode)
self.panelWrapperNode.addSubnode(self.textNode) self.panelWrapperNode.addSubnode(self.textNode)
self.panelWrapperNode.addSubnode(self.buttonNode)
if displayUndo { if displayUndo {
self.panelWrapperNode.addSubnode(self.buttonTextNode) self.panelWrapperNode.addSubnode(self.undoButtonTextNode)
self.panelWrapperNode.addSubnode(self.buttonNode) self.panelWrapperNode.addSubnode(self.undoButtonNode)
} }
self.addSubnode(self.panelNode) self.addSubnode(self.panelNode)
self.addSubnode(self.panelWrapperNode) self.addSubnode(self.panelWrapperNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in self.undoButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
if highlighted { if highlighted {
strongSelf.buttonTextNode.layer.removeAnimation(forKey: "opacity") strongSelf.undoButtonTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.buttonTextNode.alpha = 0.4 strongSelf.undoButtonTextNode.alpha = 0.4
} else { } else {
strongSelf.buttonTextNode.alpha = 1.0 strongSelf.undoButtonTextNode.alpha = 1.0
strongSelf.buttonTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) strongSelf.undoButtonTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
} }
} }
} }
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.undoButtonNode.addTarget(self, action: #selector(self.undoButtonPressed), forControlEvents: .touchUpInside)
} }
override func didLoad() { override func didLoad() {
@ -230,7 +240,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} }
@objc private func buttonPressed() { @objc private func buttonPressed() {
self.action(false) if self.action(.info) {
self.dismiss()
}
}
@objc private func undoButtonPressed() {
self.action(.undo)
self.dismiss() self.dismiss()
} }
@ -239,7 +255,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.remainingSeconds -= 1 self.remainingSeconds -= 1
} }
if self.remainingSeconds == 0 { if self.remainingSeconds == 0 {
self.action(true) self.action(.commit)
self.dismiss() self.dismiss()
} else { } else {
if !self.timerTextNode.bounds.size.width.isZero, let snapshot = self.timerTextNode.view.snapshotContentTree() { if !self.timerTextNode.bounds.size.width.isZero, let snapshot = self.timerTextNode.view.snapshotContentTree() {
@ -286,9 +302,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let margin: CGFloat = 16.0 let margin: CGFloat = 16.0
let buttonTextSize = self.buttonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
let buttonMinX: CGFloat let buttonMinX: CGFloat
if self.buttonNode.supernode != nil { if self.undoButtonNode.supernode != nil {
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset - buttonTextSize.width - margin * 2.0 buttonMinX = layout.size.width - layout.safeInsets.left - rightInset - buttonTextSize.width - margin * 2.0
} else { } else {
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset buttonMinX = layout.size.width - layout.safeInsets.left - rightInset
@ -316,8 +332,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight) self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - margin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize) let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - margin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
transition.updateFrame(node: self.buttonTextNode, frame: buttonTextFrame) transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame)
self.buttonNode.frame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - margin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + margin, height: contentHeight))
let undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - margin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + margin, height: contentHeight))
self.undoButtonNode.frame = undoButtonFrame
self.buttonNode.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: undoButtonFrame.minX - layout.safeInsets.left, height: contentHeight))
var textContentHeight = textSize.height var textContentHeight = textSize.height
var textOffset: CGFloat = 0.0 var textOffset: CGFloat = 0.0

View File

@ -177,19 +177,31 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
if component.count == 6, component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil, let color = UIColor(hexString: component) { if component.count == 6, component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil, let color = UIColor(hexString: component) {
parameter = .color(color) parameter = .color(color)
} else if component.count == 13, component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF-").inverted) == nil { } else if component.count == 13, component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF-").inverted) == nil {
var rotation: Int32?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "rotation" {
rotation = Int32(value)
}
}
}
}
let components = component.components(separatedBy: "-") let components = component.components(separatedBy: "-")
if components.count == 2, let topColor = UIColor(hexString: components[0]), let bottomColor = UIColor(hexString: components[1]) { if components.count == 2, let topColor = UIColor(hexString: components[0]), let bottomColor = UIColor(hexString: components[1]) {
parameter = .gradient(topColor, bottomColor) parameter = .gradient(topColor, bottomColor, rotation)
} else { } else {
return nil return nil
} }
} else { } else {
var options: WallpaperPresentationOptions = [] var options: WallpaperPresentationOptions = []
var intensity: Int32? var intensity: Int32?
var color: UIColor? var topColor: UIColor?
var bottomColor: UIColor?
var rotation: Int32?
if let queryItems = components.queryItems { if let queryItems = components.queryItems {
for queryItem in queryItems { for queryItem in queryItems {
if let value = queryItem.value{ if let value = queryItem.value {
if queryItem.name == "mode" { if queryItem.name == "mode" {
for option in value.components(separatedBy: "+") { for option in value.components(separatedBy: "+") {
switch option.lowercased() { switch option.lowercased() {
@ -202,14 +214,24 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
} }
} }
} else if queryItem.name == "bg_color" { } else if queryItem.name == "bg_color" {
color = UIColor(hexString: value) if value.count == 6, value.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil, let color = UIColor(hexString: value) {
topColor = color
} else if value.count == 13, value.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF-").inverted) == nil {
let components = value.components(separatedBy: "-")
if components.count == 2, let topColorValue = UIColor(hexString: components[0]), let bottomColorValue = UIColor(hexString: components[1]) {
topColor = topColorValue
bottomColor = bottomColorValue
}
}
} else if queryItem.name == "intensity" { } else if queryItem.name == "intensity" {
intensity = Int32(value) intensity = Int32(value)
} else if queryItem.name == "rotation" {
rotation = Int32(value)
} }
} }
} }
} }
parameter = .slug(component, options, color, intensity) parameter = .slug(component, options, topColor, bottomColor, intensity, rotation)
} }
return .wallpaper(parameter) return .wallpaper(parameter)
} else if pathComponents[0] == "addtheme" { } else if pathComponents[0] == "addtheme" {

View File

@ -547,7 +547,7 @@ public func solidColorImage(_ color: UIColor) -> Signal<(TransformImageArguments
}) })
} }
public func gradientImage(_ colors: [UIColor], rotation: Int32 = 0) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { public func gradientImage(_ colors: [UIColor], rotation: Int32? = nil) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
guard !colors.isEmpty else { guard !colors.isEmpty else {
return .complete() return .complete()
} }
@ -572,9 +572,11 @@ public func gradientImage(_ colors: [UIColor], rotation: Int32 = 0) -> Signal<(T
let colorSpace = CGColorSpaceCreateDeviceRGB() let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.translateBy(x: arguments.drawingSize.width / 2.0, y: arguments.drawingSize.height / 2.0) if let rotation = rotation {
c.rotate(by: CGFloat(rotation) * CGFloat.pi / 180.0) c.translateBy(x: arguments.drawingSize.width / 2.0, y: arguments.drawingSize.height / 2.0)
c.translateBy(x: -arguments.drawingSize.width / 2.0, y: -arguments.drawingSize.height / 2.0) c.rotate(by: CGFloat(rotation) * CGFloat.pi / 180.0)
c.translateBy(x: -arguments.drawingSize.width / 2.0, y: -arguments.drawingSize.height / 2.0)
}
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
} }