Cloud Themes wallpapers improvements

This commit is contained in:
Ilya Laktyushin 2019-08-29 22:08:13 +03:00
parent c05c59a1b7
commit 2c624975c5
13 changed files with 277 additions and 77 deletions

View File

@ -226,20 +226,24 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
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(theme):
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) {
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
return theme.withUpdated(name: nil, author: nil, defaultWallpaper: wallpaper?.wallpaper)
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))
previewThemePromise.set(.single(theme.withUpdated(name: nil, author: nil, defaultWallpaper: info.resolvedWallpaper)))
}
} else {
previewThemePromise.set(.single(context.sharedContext.currentPresentationData.with { $0 }.theme))
}
initialState = EditThemeControllerState(mode: mode, title: theme.theme.title, slug: theme.theme.slug, 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)
@ -373,9 +377,13 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
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, size: size)
drawThemeImage(context: c, theme: theme, wallpaperImage: wallpaperImage, size: size)
})?.cgImage {
context.draw(image, in: CGRect(origin: CGPoint(), size: size))
}
@ -410,17 +418,14 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
} 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: {

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)
@ -22,6 +23,7 @@ public final class ThemePreviewController: ViewController {
private let previewTheme: PresentationTheme
private let source: ThemePreviewSource
private let theme = Promise<TelegramTheme?>()
private let wallpaper = Promise<TelegramWallpaper>()
private var controllerNode: ThemePreviewControllerNode {
return self.displayNode as! ThemePreviewControllerNode
@ -57,6 +59,15 @@ public final class ThemePreviewController: ViewController {
themeName = previewTheme.name.string
}
if case let .file(file) = previewTheme.chat.defaultWallpaper, file.id == 0 {
self.wallpaper.set(cachedWallpaper(account: context.account, slug: file.slug)
|> mapToSignal { wallpaper in
return .single(wallpaper?.wallpaper ?? .color(Int32(bitPattern: previewTheme.chatList.backgroundColor.rgb)))
})
} else {
self.wallpaper.set(.single(previewTheme.chat.defaultWallpaper))
}
if let author = previewTheme.author {
let titleView = CounterContollerTitleView(theme: self.previewTheme)
titleView.title = CounterContollerTitle(title: themeName, counter: author)

View File

@ -30,6 +30,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,13 +42,15 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var chatNodes: [ListViewItemNode]?
private let maskNode: ASImageNode
private let chatBackgroundNode: WallpaperBackgroundNode
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?
init(context: AccountContext, previewTheme: PresentationTheme, dismiss: @escaping () -> Void, apply: @escaping () -> Void) {
@ -63,17 +67,20 @@ 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.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.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings)
@ -94,7 +101,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 +114,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.addSubnode(self.toolbarNode)
self.scrollNode.addSubnode(self.chatListBackgroundNode)
self.scrollNode.addSubnode(self.chatBackgroundNode)
self.scrollNode.addSubnode(self.instantChatBackgroundNode)
self.scrollNode.addSubnode(self.remoteChatBackgroundNode)
self.toolbarNode.cancel = {
dismiss()
@ -116,7 +124,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 {
@ -294,7 +305,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.instantChatBackgroundNode.addSubnode(itemNode!)
}
self.messageNodes = messageNodes
}
@ -315,7 +326,8 @@ 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.instantChatBackgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height)
self.remoteChatBackgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height)
self.scrollNode.view.contentSize = CGSize(width: bounds.width * 2.0, height: bounds.height)

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

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

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

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

@ -641,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 {
@ -656,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
}
@ -722,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)