mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-30 23:20:28 +00:00
Merge commit '569c3d6ac60ddfc3fe8404b1bfe9bb8c13024dc5'
This commit is contained in:
commit
628eb10f2e
@ -176,12 +176,11 @@ private struct EditThemeControllerState: Equatable {
|
||||
var mode: EditThemeControllerMode
|
||||
var title: String
|
||||
var slug: String
|
||||
var previewTheme: PresentationTheme
|
||||
var updatedTheme: PresentationTheme?
|
||||
var updating: Bool
|
||||
}
|
||||
|
||||
private func editThemeControllerEntries(presentationData: PresentationData, state: EditThemeControllerState) -> [EditThemeControllerEntry] {
|
||||
private func editThemeControllerEntries(presentationData: PresentationData, state: EditThemeControllerState, previewTheme: PresentationTheme) -> [EditThemeControllerEntry] {
|
||||
var entries: [EditThemeControllerEntry] = []
|
||||
|
||||
var isCreate = false
|
||||
@ -196,7 +195,7 @@ private func editThemeControllerEntries(presentationData: PresentationData, stat
|
||||
}
|
||||
|
||||
entries.append(.chatPreviewHeader(presentationData.theme, presentationData.strings.EditTheme_Preview.uppercased()))
|
||||
entries.append(.chatPreview(presentationData.theme, state.previewTheme, state.previewTheme.chat.defaultWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder))
|
||||
entries.append(.chatPreview(presentationData.theme, previewTheme, previewTheme.chat.defaultWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder))
|
||||
|
||||
let uploadText: String
|
||||
let uploadInfo: String
|
||||
@ -221,18 +220,30 @@ private func editThemeControllerEntries(presentationData: PresentationData, stat
|
||||
|
||||
public func editThemeController(context: AccountContext, mode: EditThemeControllerMode, navigateToChat: ((PeerId) -> Void)? = nil) -> ViewController {
|
||||
let initialState: EditThemeControllerState
|
||||
let previewThemePromise = Promise<PresentationTheme>()
|
||||
switch mode {
|
||||
case .create:
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
initialState = EditThemeControllerState(mode: mode, title: "", slug: "", previewTheme: presentationData.theme.withUpdated(name: "", author: nil, defaultWallpaper: presentationData.chatWallpaper), updatedTheme: nil, updating: false)
|
||||
case let .edit(theme):
|
||||
let previewTheme: PresentationTheme
|
||||
if let file = theme.theme.file, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let theme = makePresentationTheme(data: data, resolvedWallpaper: theme.resolvedWallpaper) {
|
||||
previewTheme = theme
|
||||
initialState = EditThemeControllerState(mode: mode, title: "", slug: "", updatedTheme: nil, updating: false)
|
||||
previewThemePromise.set(.single(presentationData.theme.withUpdated(name: "", author: nil, defaultWallpaper: presentationData.chatWallpaper)))
|
||||
case let .edit(info):
|
||||
if let file = info.theme.file, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let theme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) {
|
||||
if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
|
||||
previewThemePromise.set(cachedWallpaper(account: context.account, slug: file.slug)
|
||||
|> map ({ wallpaper -> PresentationTheme in
|
||||
if let wallpaper = wallpaper {
|
||||
return theme.withUpdated(name: nil, author: nil, defaultWallpaper: wallpaper.wallpaper)
|
||||
} else {
|
||||
return theme.withUpdated(name: nil, author: nil, defaultWallpaper: .color(Int32(bitPattern: theme.chatList.backgroundColor.rgb)))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
previewThemePromise.set(.single(theme.withUpdated(name: nil, author: nil, defaultWallpaper: info.resolvedWallpaper)))
|
||||
}
|
||||
} else {
|
||||
previewTheme = context.sharedContext.currentPresentationData.with { $0 }.theme
|
||||
previewThemePromise.set(.single(context.sharedContext.currentPresentationData.with { $0 }.theme))
|
||||
}
|
||||
initialState = EditThemeControllerState(mode: mode, title: theme.theme.title, slug: theme.theme.slug, previewTheme: previewTheme, updatedTheme: nil, updating: false)
|
||||
initialState = EditThemeControllerState(mode: mode, title: info.theme.title, slug: info.theme.slug, updatedTheme: nil, updating: false)
|
||||
}
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
@ -249,22 +260,50 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
}, openFile: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = legacyICloudFilePicker(theme: presentationData.theme, mode: .import, documentTypes: ["org.telegram.Telegram-iOS.theme"], completion: { urls in
|
||||
if let url = urls.first, let data = try? Data(contentsOf: url), let theme = makePresentationTheme(data: data) {
|
||||
updateState { current in
|
||||
var state = current
|
||||
state.previewTheme = theme
|
||||
state.updatedTheme = theme
|
||||
return state
|
||||
if let url = urls.first{
|
||||
if let data = try? Data(contentsOf: url), let theme = makePresentationTheme(data: data) {
|
||||
if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
|
||||
let _ = (cachedWallpaper(account: context.account, slug: file.slug)
|
||||
|> mapToSignal { wallpaper -> Signal<TelegramWallpaper?, NoError> in
|
||||
if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper {
|
||||
var convertedRepresentations: [ImageRepresentationWithReference] = []
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .wallpaper(resource: file.file.resource)))
|
||||
return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|
||||
|> map { _ -> TelegramWallpaper? in
|
||||
return wallpaper.wallpaper
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { wallpaper in
|
||||
let updatedTheme = theme.withUpdated(name: nil, author: nil, defaultWallpaper: wallpaper)
|
||||
updateState { current in
|
||||
var state = current
|
||||
previewThemePromise.set(.single(updatedTheme))
|
||||
state.updatedTheme = updatedTheme
|
||||
return state
|
||||
}
|
||||
})
|
||||
} else {
|
||||
updateState { current in
|
||||
var state = current
|
||||
previewThemePromise.set(.single(theme))
|
||||
state.updatedTheme = theme
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_FileReadError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
} else {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_FileReadError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
})
|
||||
presentControllerImpl?(controller, nil)
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get())
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<EditThemeControllerEntry>, EditThemeControllerEntry.ItemGenerationArguments)) in
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), previewThemePromise.get())
|
||||
|> map { presentationData, state, previewTheme -> (ItemListControllerState, (ItemListNodeState<EditThemeControllerEntry>, EditThemeControllerEntry.ItemGenerationArguments)) in
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
@ -293,75 +332,122 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
return state
|
||||
}
|
||||
|
||||
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 message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
|
||||
let _ = (previewThemePromise.get()
|
||||
|> deliverOnMainQueue).start(next: { previewTheme 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 message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
|
||||
|
||||
let _ = enqueueMessages(account: context.account, peerId: context.account.peerId, messages: [message]).start()
|
||||
let _ = enqueueMessages(account: context.account, peerId: context.account.peerId, messages: [message]).start()
|
||||
|
||||
if let navigateToChat = navigateToChat {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_ThemeTemplateAlert, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_SavedMessages, action: {
|
||||
if let navigateToChat = navigateToChat {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_ThemeTemplateAlert, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_SavedMessages, action: {
|
||||
completion()
|
||||
navigateToChat(context.account.peerId)
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
completion()
|
||||
})], actionLayout: .vertical), nil)
|
||||
} else {
|
||||
completion()
|
||||
navigateToChat(context.account.peerId)
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
completion()
|
||||
})], actionLayout: .vertical), nil)
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let theme: PresentationTheme?
|
||||
let hasCustomFile: Bool
|
||||
if let updatedTheme = state.updatedTheme {
|
||||
theme = updatedTheme.withUpdated(name: state.title, author: "", defaultWallpaper: nil)
|
||||
hasCustomFile = true
|
||||
} else {
|
||||
if case let .edit(info) = mode, let _ = info.theme.file {
|
||||
theme = nil
|
||||
|
||||
let theme: PresentationTheme?
|
||||
let hasCustomFile: Bool
|
||||
if let updatedTheme = state.updatedTheme {
|
||||
theme = updatedTheme.withUpdated(name: state.title, author: "", defaultWallpaper: nil)
|
||||
hasCustomFile = true
|
||||
} else {
|
||||
theme = state.previewTheme.withUpdated(name: state.title, author: "", defaultWallpaper: nil)
|
||||
hasCustomFile = false
|
||||
}
|
||||
}
|
||||
|
||||
let themeResource: LocalFileMediaResource?
|
||||
let themeData: Data?
|
||||
let themeThumbnailData: Data?
|
||||
if let theme = theme, let themeString = encodePresentationTheme(theme), let data = themeString.data(using: .utf8) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
|
||||
themeResource = resource
|
||||
themeData = data
|
||||
|
||||
let themeThumbnail = generateImage(CGSize(width: 213, height: 320.0), contextGenerator: { size, context in
|
||||
if let image = generateImage(CGSize(width: 194.0, height: 291.0), contextGenerator: { size, c in
|
||||
drawThemeImage(context: c, theme: theme, size: size)
|
||||
})?.cgImage {
|
||||
context.draw(image, in: CGRect(origin: CGPoint(), size: size))
|
||||
if case let .edit(info) = mode, let _ = info.theme.file {
|
||||
theme = nil
|
||||
hasCustomFile = true
|
||||
} else {
|
||||
theme = previewTheme.withUpdated(name: state.title, author: "", defaultWallpaper: nil)
|
||||
hasCustomFile = false
|
||||
}
|
||||
}, scale: 1.0)
|
||||
themeThumbnailData = themeThumbnail?.jpegData(compressionQuality: 0.6)
|
||||
} else {
|
||||
themeResource = nil
|
||||
themeData = nil
|
||||
themeThumbnailData = nil
|
||||
}
|
||||
|
||||
let resolvedWallpaper: TelegramWallpaper?
|
||||
if let theme = theme, case let .file(file) = theme.chat.defaultWallpaper, file.id != 0 {
|
||||
resolvedWallpaper = theme.chat.defaultWallpaper
|
||||
updateCachedWallpaper(account: context.account, wallpaper: theme.chat.defaultWallpaper)
|
||||
} else {
|
||||
resolvedWallpaper = nil
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .create:
|
||||
if let themeResource = themeResource {
|
||||
let _ = (createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData)
|
||||
}
|
||||
|
||||
let themeResource: LocalFileMediaResource?
|
||||
let themeData: Data?
|
||||
let themeThumbnailData: Data?
|
||||
if let theme = theme, let themeString = encodePresentationTheme(theme), let data = themeString.data(using: .utf8) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
|
||||
themeResource = resource
|
||||
themeData = data
|
||||
|
||||
var wallpaperImage: UIImage?
|
||||
if case .file = theme.chat.defaultWallpaper {
|
||||
wallpaperImage = chatControllerBackgroundImage(theme: theme, wallpaper: theme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: false)
|
||||
}
|
||||
let themeThumbnail = generateImage(CGSize(width: 213, height: 320.0), contextGenerator: { size, context in
|
||||
if let image = generateImage(CGSize(width: 194.0, height: 291.0), contextGenerator: { size, c in
|
||||
drawThemeImage(context: c, theme: theme, wallpaperImage: wallpaperImage, size: size)
|
||||
})?.cgImage {
|
||||
context.draw(image, in: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}, scale: 1.0)
|
||||
themeThumbnailData = themeThumbnail?.jpegData(compressionQuality: 0.6)
|
||||
} else {
|
||||
themeResource = nil
|
||||
themeData = nil
|
||||
themeThumbnailData = nil
|
||||
}
|
||||
|
||||
let resolvedWallpaper: TelegramWallpaper?
|
||||
if let theme = theme, case let .file(file) = theme.chat.defaultWallpaper, file.id != 0 {
|
||||
resolvedWallpaper = theme.chat.defaultWallpaper
|
||||
updateCachedWallpaper(account: context.account, wallpaper: theme.chat.defaultWallpaper)
|
||||
} else {
|
||||
resolvedWallpaper = nil
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .create:
|
||||
if let themeResource = themeResource {
|
||||
let _ = (createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData)
|
||||
|> deliverOnMainQueue).start(next: { next in
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
|
||||
let current: PresentationThemeSettings
|
||||
if let entry = entry as? PresentationThemeSettings {
|
||||
current = entry
|
||||
} else {
|
||||
current = PresentationThemeSettings.defaultSettings
|
||||
}
|
||||
if let resource = resultTheme.file?.resource, let data = themeData {
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
|
||||
}
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
if let theme = theme {
|
||||
themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper
|
||||
}
|
||||
return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
} |> deliverOnMainQueue).start(completed: {
|
||||
if !hasCustomFile {
|
||||
saveThemeTemplateFile(state.title, themeResource, {
|
||||
dismissImpl?()
|
||||
})
|
||||
} else {
|
||||
dismissImpl?()
|
||||
}
|
||||
})
|
||||
}
|
||||
}, error: { error in
|
||||
arguments.updateState { current in
|
||||
var state = current
|
||||
state.updating = false
|
||||
return state
|
||||
}
|
||||
})
|
||||
}
|
||||
case let .edit(info):
|
||||
let _ = (updateTheme(account: context.account, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource)
|
||||
|> deliverOnMainQueue).start(next: { next in
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
@ -387,7 +473,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
} |> deliverOnMainQueue).start(completed: {
|
||||
if !hasCustomFile {
|
||||
if let themeResource = themeResource, !hasCustomFile {
|
||||
saveThemeTemplateFile(state.title, themeResource, {
|
||||
dismissImpl?()
|
||||
})
|
||||
@ -403,51 +489,8 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
return state
|
||||
}
|
||||
})
|
||||
}
|
||||
case let .edit(info):
|
||||
let _ = (updateTheme(account: context.account, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource)
|
||||
|> deliverOnMainQueue).start(next: { next in
|
||||
if case let .result(resultTheme) = next {
|
||||
let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start()
|
||||
let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in
|
||||
let current: PresentationThemeSettings
|
||||
if let entry = entry as? PresentationThemeSettings {
|
||||
current = entry
|
||||
} else {
|
||||
current = PresentationThemeSettings.defaultSettings
|
||||
}
|
||||
|
||||
if let resource = resultTheme.file?.resource, let data = themeData {
|
||||
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
|
||||
}
|
||||
|
||||
let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper))
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
if let theme = theme {
|
||||
themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper
|
||||
}
|
||||
|
||||
return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
} |> deliverOnMainQueue).start(completed: {
|
||||
if let themeResource = themeResource, !hasCustomFile {
|
||||
saveThemeTemplateFile(state.title, themeResource, {
|
||||
dismissImpl?()
|
||||
})
|
||||
} else {
|
||||
dismissImpl?()
|
||||
}
|
||||
})
|
||||
}
|
||||
}, error: { error in
|
||||
arguments.updateState { current in
|
||||
var state = current
|
||||
state.updating = false
|
||||
return state
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -463,7 +506,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
|
||||
}
|
||||
}
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: editThemeControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: focusItemTag, emptyStateItem: nil, animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: editThemeControllerEntries(presentationData: presentationData, state: state, previewTheme: previewTheme), style: .blocks, focusItemTag: focusItemTag, emptyStateItem: nil, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.pageControlBackgroundNode = ASDisplayNode()
|
||||
self.pageControlBackgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3)
|
||||
self.pageControlBackgroundNode.cornerRadius = 6.0
|
||||
self.pageControlBackgroundNode.cornerRadius = 8.0
|
||||
|
||||
self.pageControlNode = PageControlNode(dotColor: self.theme.chatList.unreadBadgeActiveBackgroundColor, inactiveDotColor: self.presentationData.theme.list.pageIndicatorInactiveColor)
|
||||
|
||||
@ -168,10 +168,10 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
}
|
||||
})
|
||||
|
||||
self.serviceColorDisposable = (chatServiceBackgroundColor(wallpaper: self.presentationData.theme.chat.defaultWallpaper, mediaBox: context.account.postbox.mediaBox)
|
||||
self.serviceColorDisposable = (chatServiceBackgroundColor(wallpaper: self.presentationData.chatWallpaper, mediaBox: context.account.postbox.mediaBox)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] color in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.presentationData.theme.chat.defaultWallpaper.hasWallpaper {
|
||||
if strongSelf.presentationData.chatWallpaper.hasWallpaper {
|
||||
strongSelf.pageControlBackgroundNode.backgroundColor = color
|
||||
} else {
|
||||
strongSelf.pageControlBackgroundNode.backgroundColor = .clear
|
||||
|
@ -10,6 +10,7 @@ import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import ShareController
|
||||
import CounterContollerTitleView
|
||||
import WallpaperResources
|
||||
|
||||
public enum ThemePreviewSource {
|
||||
case theme(TelegramTheme)
|
||||
@ -112,11 +113,14 @@ public final class ThemePreviewController: ViewController {
|
||||
|
||||
switch strongSelf.source {
|
||||
case .theme, .slug:
|
||||
theme = strongSelf.theme.get()
|
||||
|> take(1)
|
||||
|> map { theme in
|
||||
theme = combineLatest(strongSelf.theme.get() |> take(1), strongSelf.controllerNode.wallpaperPromise.get() |> take(1))
|
||||
|> map { theme, wallpaper in
|
||||
if let theme = theme {
|
||||
return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil))
|
||||
if case let .file(file) = wallpaper, file.id != 0 {
|
||||
return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper))
|
||||
} else {
|
||||
return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil))
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -164,6 +168,16 @@ public final class ThemePreviewController: ViewController {
|
||||
}
|
||||
})
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
let previewTheme = self.previewTheme
|
||||
if case let .file(file) = previewTheme.chat.defaultWallpaper, file.id == 0 {
|
||||
self.controllerNode.wallpaperPromise.set(cachedWallpaper(account: self.context.account, slug: file.slug)
|
||||
|> mapToSignal { wallpaper in
|
||||
return .single(wallpaper?.wallpaper ?? .color(Int32(bitPattern: previewTheme.chatList.backgroundColor.rgb)))
|
||||
})
|
||||
} else {
|
||||
self.controllerNode.wallpaperPromise.set(.single(previewTheme.chat.defaultWallpaper))
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStrings() {
|
||||
|
@ -9,6 +9,7 @@ import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import ChatListUI
|
||||
import WallpaperResources
|
||||
|
||||
private func generateMaskImage(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 1.0, height: 60.0), opaque: false, rotatedContext: { size, context in
|
||||
@ -30,6 +31,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let previewTheme: PresentationTheme
|
||||
private var presentationData: PresentationData
|
||||
|
||||
public let wallpaperPromise = Promise<TelegramWallpaper>()
|
||||
|
||||
private let referenceTimestamp: Int32
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
@ -40,14 +43,18 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var chatNodes: [ListViewItemNode]?
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let chatBackgroundNode: WallpaperBackgroundNode
|
||||
private let chatContainerNode: ASDisplayNode
|
||||
private let instantChatBackgroundNode: WallpaperBackgroundNode
|
||||
private let remoteChatBackgroundNode: TransformImageNode
|
||||
private var messageNodes: [ListViewItemNode]?
|
||||
|
||||
private let toolbarNode: WallpaperGalleryToolbarNode
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private var wallpaperDisposable: Disposable?
|
||||
private var colorDisposable: Disposable?
|
||||
private var statusDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, previewTheme: PresentationTheme, dismiss: @escaping () -> Void, apply: @escaping () -> Void) {
|
||||
self.context = context
|
||||
@ -63,20 +70,33 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.referenceTimestamp = Int32(calendar.date(from: components)?.timeIntervalSince1970 ?? 0.0)
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
self.pageControlBackgroundNode = ASDisplayNode()
|
||||
self.pageControlBackgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3)
|
||||
self.pageControlBackgroundNode.cornerRadius = 6.0
|
||||
self.pageControlBackgroundNode.cornerRadius = 8.0
|
||||
|
||||
self.pageControlNode = PageControlNode(dotColor: previewTheme.chatList.unreadBadgeActiveBackgroundColor, inactiveDotColor: previewTheme.list.pageIndicatorInactiveColor)
|
||||
|
||||
self.chatListBackgroundNode = ASDisplayNode()
|
||||
self.chatBackgroundNode = WallpaperBackgroundNode()
|
||||
self.chatBackgroundNode.displaysAsynchronously = false
|
||||
self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: previewTheme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
|
||||
self.chatBackgroundNode.motionEnabled = previewTheme.chat.defaultWallpaper.settings?.motion ?? false
|
||||
|
||||
self.chatContainerNode = ASDisplayNode()
|
||||
self.instantChatBackgroundNode = WallpaperBackgroundNode()
|
||||
self.instantChatBackgroundNode.displaysAsynchronously = false
|
||||
self.instantChatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: previewTheme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper)
|
||||
self.instantChatBackgroundNode.motionEnabled = previewTheme.chat.defaultWallpaper.settings?.motion ?? false
|
||||
|
||||
self.remoteChatBackgroundNode = TransformImageNode()
|
||||
self.remoteChatBackgroundNode.backgroundColor = previewTheme.chatList.backgroundColor
|
||||
|
||||
self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings)
|
||||
|
||||
if case let .file(file) = previewTheme.chat.defaultWallpaper, file.id == 0 {
|
||||
self.remoteChatBackgroundNode.isHidden = false
|
||||
self.toolbarNode.setDoneEnabled(false)
|
||||
} else {
|
||||
self.remoteChatBackgroundNode.isHidden = true
|
||||
}
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
self.maskNode.displaysAsynchronously = false
|
||||
self.maskNode.displayWithoutProcessing = true
|
||||
@ -94,7 +114,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.maskNode.image = generateMaskImage(color: self.previewTheme.chatList.backgroundColor)
|
||||
|
||||
if case let .color(value) = self.previewTheme.chat.defaultWallpaper {
|
||||
self.chatBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value))
|
||||
self.instantChatBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value))
|
||||
}
|
||||
|
||||
self.pageControlNode.isUserInteractionEnabled = false
|
||||
@ -107,7 +127,10 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.addSubnode(self.toolbarNode)
|
||||
|
||||
self.scrollNode.addSubnode(self.chatListBackgroundNode)
|
||||
self.scrollNode.addSubnode(self.chatBackgroundNode)
|
||||
self.scrollNode.addSubnode(self.chatContainerNode)
|
||||
|
||||
self.chatContainerNode.addSubnode(self.instantChatBackgroundNode)
|
||||
self.chatContainerNode.addSubnode(self.remoteChatBackgroundNode)
|
||||
|
||||
self.toolbarNode.cancel = {
|
||||
dismiss()
|
||||
@ -116,7 +139,10 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
apply()
|
||||
}
|
||||
|
||||
self.colorDisposable = (chatServiceBackgroundColor(wallpaper: self.previewTheme.chat.defaultWallpaper, mediaBox: context.account.postbox.mediaBox)
|
||||
self.colorDisposable = (self.wallpaperPromise.get()
|
||||
|> mapToSignal { wallpaper in
|
||||
return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.account.postbox.mediaBox)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] color in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.previewTheme.chat.defaultWallpaper.hasWallpaper {
|
||||
@ -126,10 +152,53 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.wallpaperDisposable = (self.wallpaperPromise.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] wallpaper in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if case let .file(file) = wallpaper {
|
||||
let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0)
|
||||
let displaySize = dimensions.dividedByScreenScale().integralFloor
|
||||
|
||||
var convertedRepresentations: [ImageRepresentationWithReference] = []
|
||||
for representation in file.file.previewRepresentations {
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .wallpaper(resource: representation.resource)))
|
||||
}
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .wallpaper(resource: file.file.resource)))
|
||||
|
||||
let fileReference = FileMediaReference.standalone(media: file.file)
|
||||
let signal = wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: fileReference, representations: convertedRepresentations, alwaysShowThumbnailFirst: true, autoFetchFullSize: false)
|
||||
strongSelf.remoteChatBackgroundNode.setSignal(signal)
|
||||
|
||||
let account = strongSelf.context.account
|
||||
let statusSignal = strongSelf.context.sharedContext.accountManager.mediaBox.resourceStatus(file.file.resource)
|
||||
|> take(1)
|
||||
|> mapToSignal { status -> Signal<MediaResourceStatus, NoError> in
|
||||
if case .Local = status {
|
||||
return .single(status)
|
||||
} else {
|
||||
return account.postbox.mediaBox.resourceStatus(file.file.resource)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.statusDisposable = (statusSignal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self, case .Local = status {
|
||||
strongSelf.toolbarNode.setDoneEnabled(true)
|
||||
}
|
||||
})
|
||||
|
||||
strongSelf.remoteChatBackgroundNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.colorDisposable?.dispose()
|
||||
self.wallpaperDisposable?.dispose()
|
||||
self.statusDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -294,7 +363,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
itemNode!.isUserInteractionEnabled = false
|
||||
messageNodes.append(itemNode!)
|
||||
self.chatBackgroundNode.addSubnode(itemNode!)
|
||||
self.chatContainerNode.addSubnode(itemNode!)
|
||||
}
|
||||
self.messageNodes = messageNodes
|
||||
}
|
||||
@ -315,7 +384,9 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let toolbarHeight = 49.0 + layout.intrinsicInsets.bottom
|
||||
self.chatListBackgroundNode.frame = CGRect(x: bounds.width, y: 0.0, width: bounds.width, height: bounds.height)
|
||||
self.chatBackgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height)
|
||||
self.chatContainerNode.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height)
|
||||
self.instantChatBackgroundNode.frame = self.chatContainerNode.bounds
|
||||
self.remoteChatBackgroundNode.frame = self.chatContainerNode.bounds
|
||||
|
||||
self.scrollNode.view.contentSize = CGSize(width: bounds.width * 2.0, height: bounds.height)
|
||||
|
||||
|
@ -124,9 +124,6 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
|
||||
let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3)
|
||||
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: item.strings.Appearance_PreviewReplyText, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
||||
|
||||
//let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: item.componentTheme, wallpaper: item.wallpaper), fontSize: item.fontSize, strings: item.strings, dateTimeFormat: item.dateTimeFormat, nameDisplayOrder: item.nameDisplayOrder, disableAnimations: false, largeEmoji: false)
|
||||
|
||||
//let item2: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, context: item.context, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: item.strings.Appearance_PreviewIncomingText, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), disableDate: true)
|
||||
let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: item.strings.Appearance_PreviewIncomingText, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: TelegramUser(id: item.context.account.peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []), text: item.strings.Appearance_PreviewOutgoingText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])
|
||||
|
||||
|
@ -562,7 +562,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil)) }
|
||||
|
||||
var availableThemes = defaultThemes
|
||||
if !defaultThemes.contains(settings.theme) && !cloudThemes.contains(settings.theme) {
|
||||
if defaultThemes.first(where: { $0.index == settings.theme.index }) == nil && cloudThemes.first(where: { $0.index == settings.theme.index }) == nil {
|
||||
availableThemes.append(settings.theme)
|
||||
}
|
||||
availableThemes.append(contentsOf: cloudThemes)
|
||||
|
@ -412,7 +412,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
updated = true
|
||||
}
|
||||
|
||||
let selected = theme == item.currentTheme
|
||||
let selected = theme.index == item.currentTheme.index
|
||||
if selected {
|
||||
selectedNode = imageNode
|
||||
}
|
||||
|
@ -2924,6 +2924,33 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-637606386)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
fromPeer.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(id.count))
|
||||
for item in id {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(randomId.count))
|
||||
for item in randomId {
|
||||
serializeInt64(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
toPeer.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(570955184)
|
||||
@ -2944,6 +2971,31 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1224152952)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(entities!.count))
|
||||
for item in entities! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-872345397)
|
||||
@ -2966,33 +3018,6 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-637606386)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
fromPeer.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(id.count))
|
||||
for item in id {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(randomId.count))
|
||||
for item in randomId {
|
||||
serializeInt64(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
toPeer.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func getScheduledHistory(peer: Api.InputPeer, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-490575781)
|
||||
@ -3064,31 +3089,6 @@ public extension Api {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1224152952)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(entities!.count))
|
||||
for item in entities! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct channels {
|
||||
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
@ -5683,15 +5683,16 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func updateTheme(flags: Int32, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
|
||||
public static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Theme>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-124161674)
|
||||
buffer.appendInt32(999203330)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(format, buffer: buffer, boxed: false)
|
||||
theme.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(slug!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)}
|
||||
return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", flags), ("theme", theme), ("slug", slug), ("title", title), ("document", document)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in
|
||||
return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", flags), ("format", format), ("theme", theme), ("slug", slug), ("title", title), ("document", document)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Theme?
|
||||
if let signature = reader.readInt32() {
|
||||
|
@ -1305,6 +1305,7 @@ public class Account {
|
||||
self.managedOperationsDisposable.add(managedPendingPeerNotificationSettings(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedSynchronizeAppLogEventsOperations(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedNotificationSettingsBehaviors(postbox: self.postbox).start())
|
||||
self.managedOperationsDisposable.add(managedThemesUpdates(accountManager: accountManager, postbox: self.postbox, network: self.network).start())
|
||||
|
||||
if !self.supplementary {
|
||||
self.managedOperationsDisposable.add(managedAnimatedEmojiUpdates(postbox: self.postbox, network: self.network).start())
|
||||
|
@ -2955,6 +2955,14 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
updatedEntries.append(OrderedItemListEntry(id: id, contents: theme))
|
||||
}
|
||||
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries)
|
||||
accountManager.transaction { transaction in
|
||||
transaction.updateSharedData(SharedDataKeys.themeSettings, { current in
|
||||
if let current = current as? ThemeSettings, let theme = current.currentTheme, let updatedTheme = updatedThemes[theme.id] {
|
||||
return ThemeSettings(currentTheme: updatedTheme)
|
||||
}
|
||||
return current
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
addedIncomingMessageIds.append(contentsOf: addedSecretMessageIds)
|
||||
|
@ -138,6 +138,8 @@ final class HistoryViewStateValidationContexts {
|
||||
|
||||
private var contexts: [Int32: HistoryStateValidationContext] = [:]
|
||||
|
||||
private var previousPeerValidationTimestamps: [PeerId: Double] = [:]
|
||||
|
||||
init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId) {
|
||||
self.queue = queue
|
||||
self.postbox = postbox
|
||||
@ -264,21 +266,27 @@ final class HistoryViewStateValidationContexts {
|
||||
} else if view.namespaces.contains(Namespaces.Message.ScheduledCloud) {
|
||||
if let _ = self.contexts[id] {
|
||||
} else if let location = location, case let .peer(peerId) = location {
|
||||
let context = HistoryStateValidationContext()
|
||||
self.contexts[id] = context
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
let batch = HistoryStateValidationBatch(disposable: disposable)
|
||||
context.batch = batch
|
||||
|
||||
let messages: [Message] = view.entries.map { $0.message }.filter { $0.id.namespace == Namespaces.Message.ScheduledCloud }
|
||||
let timestamp = self.network.context.globalTime()
|
||||
if let previousTimestamp = self.previousPeerValidationTimestamps[peerId], timestamp < previousTimestamp + 60 {
|
||||
} else {
|
||||
self.previousPeerValidationTimestamps[peerId] = timestamp
|
||||
|
||||
let context = HistoryStateValidationContext()
|
||||
self.contexts[id] = context
|
||||
|
||||
disposable.set((validateScheduledMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: peerId, tag: nil, messages: messages, historyState: .scheduledMessages(peerId))
|
||||
|> deliverOn(self.queue)).start(completed: { [weak self] in
|
||||
if let strongSelf = self, let context = strongSelf.contexts[id] {
|
||||
context.batch = nil
|
||||
}
|
||||
}))
|
||||
let disposable = MetaDisposable()
|
||||
let batch = HistoryStateValidationBatch(disposable: disposable)
|
||||
context.batch = batch
|
||||
|
||||
let messages: [Message] = view.entries.map { $0.message }.filter { $0.id.namespace == Namespaces.Message.ScheduledCloud }
|
||||
|
||||
disposable.set((validateScheduledMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: peerId, tag: nil, messages: messages, historyState: .scheduledMessages(peerId))
|
||||
|> deliverOn(self.queue)).start(completed: { [weak self] in
|
||||
if let strongSelf = self, let context = strongSelf.contexts[id] {
|
||||
context.batch = nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ public extension Message {
|
||||
if self.flags.contains(.Failed) {
|
||||
return true
|
||||
} else if self.id.namespace == Namespaces.Message.ScheduledCloud {
|
||||
return timestamp > self.timestamp + 30
|
||||
return timestamp > self.timestamp + 60
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -115,18 +115,18 @@ public func getTheme(account: Account, slug: String) -> Signal<TelegramTheme, Ge
|
||||
}
|
||||
}
|
||||
|
||||
public enum CheckThemeUpdatedResult {
|
||||
public enum ThemeUpdatedResult {
|
||||
case updated(TelegramTheme)
|
||||
case notModified
|
||||
}
|
||||
|
||||
public func checkThemeUpdated(account: Account, theme: TelegramTheme) -> Signal<CheckThemeUpdatedResult, GetThemeError> {
|
||||
private func checkThemeUpdated(network: Network, theme: TelegramTheme) -> Signal<ThemeUpdatedResult, GetThemeError> {
|
||||
guard let file = theme.file, let fileId = file.id?.id else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return account.network.request(Api.functions.account.getTheme(format: themeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), documentId: fileId))
|
||||
return network.request(Api.functions.account.getTheme(format: themeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), documentId: fileId))
|
||||
|> mapError { _ -> GetThemeError in return .generic }
|
||||
|> map { theme -> CheckThemeUpdatedResult in
|
||||
|> map { theme -> ThemeUpdatedResult in
|
||||
if let theme = TelegramTheme(apiTheme: theme) {
|
||||
return .updated(theme)
|
||||
} else {
|
||||
@ -264,6 +264,7 @@ private func uploadTheme(account: Account, resource: MediaResource, thumbnailDat
|
||||
|
||||
public enum CreateThemeError {
|
||||
case generic
|
||||
case slugInvalid
|
||||
}
|
||||
|
||||
public enum CreateThemeResult {
|
||||
@ -279,7 +280,12 @@ public func createTheme(account: Account, title: String, resource: MediaResource
|
||||
case let .complete(file):
|
||||
if let resource = file.resource as? CloudDocumentMediaResource {
|
||||
return account.network.request(Api.functions.account.createTheme(slug: "", title: title, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))))
|
||||
|> mapError { _ in return CreateThemeError.generic }
|
||||
|> mapError { error in
|
||||
if error.errorDescription == "THEME_SLUG_INVALID" {
|
||||
return .slugInvalid
|
||||
}
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
|
||||
if let theme = TelegramTheme(apiTheme: apiTheme) {
|
||||
return account.postbox.transaction { transaction -> CreateThemeResult in
|
||||
@ -318,7 +324,7 @@ public func updateTheme(account: Account, theme: TelegramTheme, title: String?,
|
||||
if let _ = title {
|
||||
flags |= 1 << 1
|
||||
}
|
||||
if let _ = slug {
|
||||
if let slug = slug, !slug.isEmpty {
|
||||
flags |= 1 << 0
|
||||
}
|
||||
if let _ = resource {
|
||||
@ -352,8 +358,13 @@ public func updateTheme(account: Account, theme: TelegramTheme, title: String?,
|
||||
inputDocument = nil
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.account.updateTheme(flags: flags, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument))
|
||||
|> mapError { _ in return CreateThemeError.generic }
|
||||
return account.network.request(Api.functions.account.updateTheme(flags: flags, format: themeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument))
|
||||
|> mapError { error in
|
||||
if error.errorDescription == "THEME_SLUG_INVALID" {
|
||||
return .slugInvalid
|
||||
}
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { apiTheme -> Signal<CreateThemeResult, CreateThemeError> in
|
||||
if let result = TelegramTheme(apiTheme: apiTheme) {
|
||||
return account.postbox.transaction { transaction -> CreateThemeResult in
|
||||
@ -437,3 +448,48 @@ public func applyTheme(accountManager: AccountManager, account: Account, theme:
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
func managedThemesUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
||||
return accountManager.sharedData(keys: [SharedDataKeys.themeSettings])
|
||||
|> mapToSignal { sharedData -> Signal<Void, NoError> in
|
||||
let themeSettings = (sharedData.entries[SharedDataKeys.themeSettings] as? ThemeSettings) ?? ThemeSettings(currentTheme: nil)
|
||||
if let currentTheme = themeSettings.currentTheme {
|
||||
let poll = Signal<Void, NoError> { subscriber in
|
||||
return checkThemeUpdated(network: network, theme: currentTheme).start(next: { result in
|
||||
if case let .updated(updatedTheme) = result {
|
||||
let _ = accountManager.transaction { transaction in
|
||||
transaction.updateSharedData(SharedDataKeys.themeSettings, { _ in
|
||||
return ThemeSettings(currentTheme: updatedTheme)
|
||||
})
|
||||
}.start()
|
||||
let _ = postbox.transaction { transaction in
|
||||
|
||||
}
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
}
|
||||
return ((.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue())) |> then(poll)) |> restart
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func actualizedTheme(accountManager: AccountManager, theme: TelegramTheme) -> Signal<TelegramTheme, NoError> {
|
||||
var currentTheme = theme
|
||||
return accountManager.sharedData(keys: [SharedDataKeys.themeSettings])
|
||||
|> map { sharedData -> TelegramTheme in
|
||||
let themeSettings = (sharedData.entries[SharedDataKeys.themeSettings] as? ThemeSettings) ?? ThemeSettings(currentTheme: nil)
|
||||
if let updatedTheme = themeSettings.currentTheme, updatedTheme.id == currentTheme.id {
|
||||
if updatedTheme != currentTheme {
|
||||
currentTheme = updatedTheme
|
||||
return updatedTheme
|
||||
} else {
|
||||
return currentTheme
|
||||
}
|
||||
} else {
|
||||
return theme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ extension TelegramWallpaper: Codable {
|
||||
case "builtin":
|
||||
self = .builtin(WallpaperSettings())
|
||||
default:
|
||||
if let color = UIColor(hexString: value) {
|
||||
if value.count == 6, let color = UIColor(hexString: value) {
|
||||
self = .color(Int32(bitPattern: color.rgb))
|
||||
} else {
|
||||
self = .file(id: 0, accessHash: 0, isCreator: false, isDefault: false, isPattern: false, isDark: false, slug: value, 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: nil, intensity: nil))
|
||||
|
@ -5561,7 +5561,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let _ = (enqueueMessages(account: self.context.account, peerId: peerId, messages: self.transformEnqueueMessages(messages))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
if let strongSelf = self, !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}
|
||||
})
|
||||
|
||||
self.donateIntent()
|
||||
|
@ -1075,27 +1075,43 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
}
|
||||
|
||||
public func scrollScreenToTop() {
|
||||
var currentMessage: Message?
|
||||
if let historyView = self.historyView {
|
||||
if let visibleRange = self.displayedItemRange.loadedRange {
|
||||
var index = historyView.filteredEntries.count - 1
|
||||
loop: for entry in historyView.filteredEntries {
|
||||
if index >= visibleRange.firstIndex && index <= visibleRange.lastIndex {
|
||||
if case let .MessageEntry(message, _, _, _, _, _) = entry {
|
||||
currentMessage = message
|
||||
break loop
|
||||
} else if case let .MessageGroupEntry(_, messages, _) = entry {
|
||||
currentMessage = messages.first?.0
|
||||
break loop
|
||||
}
|
||||
if let subject = self.subject, case .scheduledMessages = subject {
|
||||
if let historyView = self.historyView {
|
||||
if let entry = historyView.filteredEntries.first {
|
||||
var currentMessage: Message?
|
||||
if case let .MessageEntry(message, _, _, _, _, _) = entry {
|
||||
currentMessage = message
|
||||
} else if case let .MessageGroupEntry(_, messages, _) = entry {
|
||||
currentMessage = messages.first?.0
|
||||
}
|
||||
if let message = currentMessage, let anchorMessage = self.anchorMessageInCurrentHistoryView() {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .message(message.index), anchorIndex: .message(message.index), sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true), id: self.takeNextHistoryLocationId())
|
||||
}
|
||||
index -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let currentMessage = currentMessage {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .message(currentMessage.index), anchorIndex: .message(currentMessage.index), sourceIndex: .upperBound, scrollPosition: .top(0.0), animated: true), id: self.takeNextHistoryLocationId())
|
||||
} else {
|
||||
var currentMessage: Message?
|
||||
if let historyView = self.historyView {
|
||||
if let visibleRange = self.displayedItemRange.loadedRange {
|
||||
var index = historyView.filteredEntries.count - 1
|
||||
loop: for entry in historyView.filteredEntries {
|
||||
if index >= visibleRange.firstIndex && index <= visibleRange.lastIndex {
|
||||
if case let .MessageEntry(message, _, _, _, _, _) = entry {
|
||||
currentMessage = message
|
||||
break loop
|
||||
} else if case let .MessageGroupEntry(_, messages, _) = entry {
|
||||
currentMessage = messages.first?.0
|
||||
break loop
|
||||
}
|
||||
}
|
||||
index -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let currentMessage = currentMessage {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .message(currentMessage.index), anchorIndex: .message(currentMessage.index), sourceIndex: .upperBound, scrollPosition: .top(0.0), animated: true), id: self.takeNextHistoryLocationId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A
|
||||
type = .Generic(type: .UpdateVisible)
|
||||
}
|
||||
} else {
|
||||
type = .Generic(type: updateType)
|
||||
type = .Generic(type: .Generic)
|
||||
}
|
||||
return .HistoryView(view: view, type: type, scrollPosition: scrollPosition, flashIndicators: false, originalScrollPosition: chatScrollPosition, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import LocalMediaResources
|
||||
import WebSearchUI
|
||||
import InstantPageCache
|
||||
import SettingsUI
|
||||
import WallpaperResources
|
||||
|
||||
private var telegramUIDeclaredEncodables: Void = {
|
||||
declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) })
|
||||
@ -40,6 +41,7 @@ private var telegramUIDeclaredEncodables: Void = {
|
||||
declareEncodable(InstantPageStoredState.self, f: { InstantPageStoredState(decoder: $0) })
|
||||
declareEncodable(InstantPageStoredDetailsState.self, f: { InstantPageStoredDetailsState(decoder: $0) })
|
||||
declareEncodable(CachedInstantPage.self, f: { CachedInstantPage(decoder: $0) })
|
||||
declareEncodable(CachedWallpaper.self, f: { CachedWallpaper(decoder: $0) })
|
||||
declareEncodable(WatchPresetSettings.self, f: { WatchPresetSettings(decoder: $0) })
|
||||
declareEncodable(WebSearchSettings.self, f: { WebSearchSettings(decoder: $0) })
|
||||
declareEncodable(RecentWebSearchQueryItem.self, f: { RecentWebSearchQueryItem(decoder: $0) })
|
||||
|
@ -367,6 +367,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
return nil
|
||||
}))
|
||||
case let .theme(media):
|
||||
params.dismissInput()
|
||||
let path = params.context.account.postbox.mediaBox.completedResourcePath(media.resource)
|
||||
var previewTheme: PresentationTheme?
|
||||
if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) {
|
||||
@ -462,9 +463,8 @@ func openChatTheme(context: AccountContext, message: Message, present: @escaping
|
||||
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url)
|
||||
|> deliverOnMainQueue).start(next: { resolvedUrl in
|
||||
if case let .theme(slug) = resolvedUrl {
|
||||
let file = content.files!.first!
|
||||
if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let theme = makePresentationTheme(data: data) {
|
||||
if case let .theme(slug) = resolvedUrl, let files = content.files {
|
||||
if let file = files.filter({ $0.mimeType == "application/x-tgtheme-ios" }).first, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let theme = makePresentationTheme(data: data) {
|
||||
let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file))
|
||||
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
|
@ -98,9 +98,17 @@ public enum PresentationThemeReference: PostboxCoding, Equatable {
|
||||
case 0:
|
||||
self = .builtin(PresentationBuiltinThemeReference(rawValue: decoder.decodeInt32ForKey("t", orElse: 0))!)
|
||||
case 1:
|
||||
self = .local(decoder.decodeObjectForKey("localTheme", decoder: { PresentationLocalTheme(decoder: $0) }) as! PresentationLocalTheme)
|
||||
if let localTheme = decoder.decodeObjectForKey("localTheme", decoder: { PresentationLocalTheme(decoder: $0) }) as? PresentationLocalTheme {
|
||||
self = .local(localTheme)
|
||||
} else {
|
||||
self = .builtin(.dayClassic)
|
||||
}
|
||||
case 2:
|
||||
self = .cloud(decoder.decodeObjectForKey("cloudTheme", decoder: { PresentationCloudTheme(decoder: $0) }) as! PresentationCloudTheme)
|
||||
if let cloudTheme = decoder.decodeObjectForKey("cloudTheme", decoder: { PresentationCloudTheme(decoder: $0) }) as? PresentationCloudTheme {
|
||||
self = .cloud(cloudTheme)
|
||||
} else {
|
||||
self = .builtin(.dayClassic)
|
||||
}
|
||||
default:
|
||||
assertionFailure()
|
||||
self = .builtin(.dayClassic)
|
||||
@ -438,7 +446,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin(WallpaperSettings())
|
||||
self.theme = decoder.decodeObjectForKey("t", decoder: { PresentationThemeReference(decoder: $0) }) as! PresentationThemeReference
|
||||
self.theme = decoder.decodeObjectForKey("t", decoder: { PresentationThemeReference(decoder: $0) }) as? PresentationThemeReference ?? .builtin(.dayClassic)
|
||||
|
||||
self.themeSpecificChatWallpapers = decoder.decodeObjectDictionaryForKey("themeSpecificChatWallpapers", keyDecoder: { decoder in
|
||||
return decoder.decodeInt64ForKey("k", orElse: 0)
|
||||
|
@ -11,7 +11,7 @@ import PhotoResources
|
||||
import LocalMediaResources
|
||||
import TelegramPresentationData
|
||||
|
||||
private func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
||||
private func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
||||
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) {
|
||||
|
||||
let maybeFullSize: Signal<MediaResourceData, NoError>
|
||||
@ -143,10 +143,15 @@ private func wallpaperDatas(account: Account, accountManager: AccountManager, fi
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
return thumbnailData |> mapToSignal { thumbnailData in
|
||||
if onlyFullSize {
|
||||
return fullSizeData |> map { (fullSizeData, complete) in
|
||||
return (thumbnailData, fullSizeData, complete)
|
||||
return (nil, fullSizeData, complete)
|
||||
}
|
||||
} else {
|
||||
return thumbnailData |> mapToSignal { thumbnailData in
|
||||
return fullSizeData |> map { (fullSizeData, complete) in
|
||||
return (thumbnailData, fullSizeData, complete)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,11 +163,14 @@ private func wallpaperDatas(account: Account, accountManager: AccountManager, fi
|
||||
}
|
||||
}
|
||||
|
||||
public func wallpaperImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let signal = wallpaperDatas(account: account, accountManager: accountManager, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, thumbnail: thumbnail, autoFetchFullSize: autoFetchFullSize, synchronousLoad: synchronousLoad)
|
||||
public func wallpaperImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let signal = wallpaperDatas(account: account, accountManager: accountManager, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, thumbnail: thumbnail, onlyFullSize: onlyFullSize, autoFetchFullSize: autoFetchFullSize, synchronousLoad: synchronousLoad)
|
||||
|
||||
return signal
|
||||
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in
|
||||
if let fullSizeData = fullSizeData, let fileReference = fileReference {
|
||||
accountManager.mediaBox.storeResourceData(fileReference.media.resource.id, data: fullSizeData)
|
||||
}
|
||||
return { arguments in
|
||||
let drawingRect = arguments.drawingRect
|
||||
var fittedSize = arguments.imageSize
|
||||
@ -633,7 +641,7 @@ private func generateBackArrowImage(color: UIColor) -> UIImage? {
|
||||
})
|
||||
}
|
||||
|
||||
public func drawThemeImage(context c: CGContext, theme: PresentationTheme, size: CGSize) {
|
||||
public func drawThemeImage(context c: CGContext, theme: PresentationTheme, wallpaperImage: UIImage? = nil, size: CGSize) {
|
||||
let drawingRect = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
switch theme.chat.defaultWallpaper {
|
||||
@ -648,6 +656,11 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, size:
|
||||
case let .file(file):
|
||||
c.setFillColor(theme.chatList.backgroundColor.cgColor)
|
||||
c.fill(drawingRect)
|
||||
|
||||
if let image = wallpaperImage, let cgImage = image.cgImage {
|
||||
let size = image.size.aspectFilled(drawingRect.size)
|
||||
c.draw(cgImage, in: CGRect(origin: CGPoint(x: (drawingRect.size.width - size.width) / 2.0, y: (drawingRect.size.height - size.height) / 2.0), size: size))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -714,30 +727,162 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, size:
|
||||
}
|
||||
|
||||
public func themeImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
return telegramThemeData(account: account, accountManager: accountManager, resource: fileReference.media.resource, synchronousLoad: synchronousLoad)
|
||||
|> map { data in
|
||||
let theme: PresentationTheme?
|
||||
if let data = data {
|
||||
theme = makePresentationTheme(data: data)
|
||||
let maybeFetched = accountManager.mediaBox.resourceData(fileReference.media.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|
||||
let data = maybeFetched
|
||||
|> take(1)
|
||||
|> mapToSignal { maybeData -> Signal<(Data?, Data?), NoError> in
|
||||
if maybeData.complete {
|
||||
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
|
||||
return .single((loadedData, nil))
|
||||
} else {
|
||||
theme = nil
|
||||
let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
|
||||
|
||||
let previewRepresentation = fileReference.media.previewRepresentations.first
|
||||
let fetchedThumbnail: Signal<FetchResourceSourceType, FetchResourceError>
|
||||
if let previewRepresentation = previewRepresentation {
|
||||
fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(previewRepresentation.resource))
|
||||
} else {
|
||||
fetchedThumbnail = .complete()
|
||||
}
|
||||
|
||||
let thumbnailData: Signal<Data?, NoError>
|
||||
if let previewRepresentation = previewRepresentation {
|
||||
thumbnailData = Signal<Data?, NoError> { subscriber in
|
||||
let fetchedDisposable = fetchedThumbnail.start()
|
||||
let thumbnailDisposable = account.postbox.mediaBox.resourceData(previewRepresentation.resource).start(next: { next in
|
||||
let data = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])
|
||||
if let data = data, data.count > 0 {
|
||||
subscriber.putNext(data)
|
||||
} else {
|
||||
subscriber.putNext(decodedThumbnailData)
|
||||
}
|
||||
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||
|
||||
return ActionDisposable {
|
||||
fetchedDisposable.dispose()
|
||||
thumbnailDisposable.dispose()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
thumbnailData = .single(decodedThumbnailData)
|
||||
}
|
||||
|
||||
let reference = fileReference.resourceReference(fileReference.media.resource)
|
||||
let fullSizeData = Signal<Data?, NoError> { subscriber in
|
||||
let fetch = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: reference).start()
|
||||
let disposable = (account.postbox.mediaBox.resourceData(reference.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false)
|
||||
|> map { data -> Data? in
|
||||
return data.complete ? try? Data(contentsOf: URL(fileURLWithPath: data.path)) : nil
|
||||
}).start(next: { next in
|
||||
if let data = next {
|
||||
accountManager.mediaBox.storeResourceData(reference.resource.id, data: data)
|
||||
}
|
||||
subscriber.putNext(next)
|
||||
}, error: { error in
|
||||
subscriber.putError(error)
|
||||
}, completed: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
return ActionDisposable {
|
||||
fetch.dispose()
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
return thumbnailData |> mapToSignal { thumbnailData in
|
||||
return fullSizeData |> map { fullSizeData in
|
||||
return (fullSizeData, thumbnailData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapToSignal { (fullSizeData, thumbnailData) -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in
|
||||
if let fullSizeData = fullSizeData, let theme = makePresentationTheme(data: fullSizeData) {
|
||||
if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 {
|
||||
return cachedWallpaper(account: account, slug: file.slug)
|
||||
|> mapToSignal { wallpaper -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in
|
||||
if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper {
|
||||
var convertedRepresentations: [ImageRepresentationWithReference] = []
|
||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .wallpaper(resource: file.file.resource)))
|
||||
return wallpaperImage(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|
||||
|> map { _ -> (PresentationTheme?, UIImage?, Data?) in
|
||||
if let path = accountManager.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let image = UIImage(data: data) {
|
||||
return (theme, image, thumbnailData)
|
||||
} else {
|
||||
return (theme, nil, thumbnailData)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single((theme, nil, thumbnailData))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single((theme, nil, thumbnailData))
|
||||
}
|
||||
} else {
|
||||
return .single((nil, nil, thumbnailData))
|
||||
}
|
||||
}
|
||||
return data
|
||||
|> map { theme, wallpaperImage, thumbnailData in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: 0.0, clear: true)
|
||||
var thumbnailImage: CGImage?
|
||||
if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
|
||||
thumbnailImage = image
|
||||
}
|
||||
|
||||
var blurredThumbnailImage: UIImage?
|
||||
if let thumbnailImage = thumbnailImage {
|
||||
let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height)
|
||||
if thumbnailSize.width > 200.0 {
|
||||
blurredThumbnailImage = UIImage(cgImage: thumbnailImage)
|
||||
} else {
|
||||
let initialThumbnailContextFittingSize = arguments.imageSize.fitted(CGSize(width: 90.0, height: 90.0))
|
||||
|
||||
let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize)
|
||||
let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0)
|
||||
thumbnailContext.withFlippedContext { c in
|
||||
c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
|
||||
}
|
||||
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
|
||||
|
||||
var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5))
|
||||
if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 {
|
||||
thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0))
|
||||
}
|
||||
|
||||
blurredThumbnailImage = thumbnailContext.generateImage()
|
||||
}
|
||||
}
|
||||
|
||||
let drawingRect = arguments.drawingRect
|
||||
context.withFlippedContext { c in
|
||||
c.setBlendMode(.normal)
|
||||
if let theme = theme {
|
||||
drawThemeImage(context: c, theme: theme, size: arguments.drawingSize)
|
||||
if let blurredThumbnailImage = blurredThumbnailImage, theme == nil {
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: 0.0, clear: true)
|
||||
context.withFlippedContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
if let cgImage = blurredThumbnailImage.cgImage {
|
||||
c.interpolationQuality = .none
|
||||
let fittedSize = blurredThumbnailImage.size.aspectFilled(arguments.drawingSize)
|
||||
drawImage(context: c, image: cgImage, orientation: .up, in: CGRect(origin: CGPoint(x: (drawingRect.width - fittedSize.width) / 2.0, y: (drawingRect.height - fittedSize.height) / 2.0), size: fittedSize))
|
||||
c.setBlendMode(.normal)
|
||||
}
|
||||
}
|
||||
addCorners(context, arguments: arguments)
|
||||
return context
|
||||
}
|
||||
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: 0.0, clear: true)
|
||||
if let theme = theme {
|
||||
context.withFlippedContext { c in
|
||||
c.setBlendMode(.normal)
|
||||
|
||||
drawThemeImage(context: c, theme: theme, wallpaperImage: wallpaperImage, size: arguments.drawingSize)
|
||||
|
||||
c.setStrokeColor(theme.rootController.navigationBar.separatorColor.cgColor)
|
||||
c.setLineWidth(2.0)
|
||||
let borderPath = UIBezierPath(roundedRect: drawingRect, cornerRadius: 4.0)
|
||||
c.addPath(borderPath.cgPath)
|
||||
c.drawPath(using: .stroke)
|
||||
} else if let emptyColor = arguments.emptyColor {
|
||||
c.setFillColor(emptyColor.cgColor)
|
||||
c.fill(drawingRect)
|
||||
}
|
||||
}
|
||||
addCorners(context, arguments: arguments)
|
||||
|
Loading…
x
Reference in New Issue
Block a user