Merge commit '569c3d6ac60ddfc3fe8404b1bfe9bb8c13024dc5'

This commit is contained in:
Peter 2019-08-30 01:06:25 +04:00
commit 628eb10f2e
21 changed files with 650 additions and 278 deletions

View File

@ -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))
}

View File

@ -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

View File

@ -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() {

View File

@ -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)

View File

@ -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: [])

View File

@ -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)

View File

@ -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
}

View File

@ -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() {

View File

@ -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())

View File

@ -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)

View File

@ -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
}
}))
}
}
}
}

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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))

View File

@ -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()

View File

@ -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())
}
}
}

View File

@ -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)
}

View File

@ -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) })

View File

@ -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))
}

View File

@ -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)

View File

@ -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)