Chat wallpaper improvements

This commit is contained in:
Ilya Laktyushin 2023-04-10 18:54:40 +04:00
parent 15ecbd1136
commit 4b6f32fc17
14 changed files with 531 additions and 50 deletions

View File

@ -197,15 +197,18 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE
}
public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) {
let imageSignal: Signal<UIImage, NoError>
var imageSignal: Signal<UIImage, NoError>
switch wallpaper {
case let .wallpaper(wallpaper, _):
imageSignal = .complete()
switch wallpaper {
case let .file(file):
if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let image = UIImage(data: data) {
context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data, synchronous: true)
let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start()
let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start()
imageSignal = .single(image)
}
case let .image(representations, _):
for representation in representations {
@ -218,7 +221,6 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa
default:
break
}
imageSignal = .complete()
completion()
case let .asset(asset):
imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false)
@ -299,31 +301,11 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa
let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: intensity)
let temporaryWallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), TelegramMediaImageRepresentation(dimensions: PixelDimensions(croppedImage.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], settings)
let _ = context.account.postbox.transaction({ transaction in
transaction.updatePeerCachedData(peerIds: Set([peerId])) { _, cachedData in
if let cachedData = cachedData as? CachedUserData {
return cachedData.withUpdatedWallpaper(temporaryWallpaper)
} else {
return cachedData
}
}
}).start()
Queue.mainQueue().async {
completion()
}
let _ = uploadWallpaper(account: context.account, resource: resource, settings: WallpaperSettings(blur: false, motion: mode.contains(.motion), colors: [], intensity: intensity), forChat: true).start(next: { status in
if case let .complete(wallpaper) = status {
if case let .file(file) = wallpaper {
context.account.postbox.mediaBox.copyResourceData(from: resource.id, to: file.file.resource.id, synchronous: true)
for representation in file.file.previewRepresentations {
context.account.postbox.mediaBox.copyResourceData(from: resource.id, to: representation.resource.id, synchronous: true)
}
}
let _ = context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: wallpaper).start()
}
})
context.account.pendingPeerMediaUploadManager.add(peerId: peerId, content: .wallpaper(temporaryWallpaper))
}
return croppedImage
}).start()

View File

@ -142,7 +142,7 @@ public final class ThemePreviewController: ViewController {
let titleView = CounterContollerTitleView(theme: self.previewTheme)
titleView.title = CounterContollerTitle(title: themeName, counter: hasInstallsCount ? " " : "")
self.navigationItem.titleView = titleView
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
self.statusBar.statusBarStyle = self.previewTheme.rootController.statusBarStyle.style
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
@ -179,6 +179,10 @@ public final class ThemePreviewController: ViewController {
self.applyDisposable.dispose()
}
@objc private func cancelPressed() {
self.dismiss(animated: true)
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

View File

@ -244,6 +244,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
guard let strongSelf = self else {
return
}
var useDarkButton = true
if case let .file(file) = wallpaper {
let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100)
let displaySize = dimensions.cgSize.dividedByScreenScale().integralFloor
@ -258,6 +259,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
if wallpaper.isPattern {
signal = .complete()
} else {
useDarkButton = false
signal = .complete()
}
strongSelf.remoteChatBackgroundNode.setSignal(signal)
@ -296,6 +298,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
strongSelf.remoteChatBackgroundNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), custom: patternArguments))()
strongSelf.toolbarNode.dark = useDarkButton
}
})
}

View File

@ -176,7 +176,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.patternButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Pattern, value: .check(false))
self.patternButtonNode.setEnabled(false)
self.serviceBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.33))
self.serviceBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.45))
self.serviceBackgroundNode.isHidden = true
var sliderValueChangedImpl: ((CGFloat) -> Void)?
@ -1567,7 +1567,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
if let strongSelf = self, (count < 2 && currentTimestamp > timestamp + 24 * 60 * 60) {
strongSelf.displayedPreviewTooltip = true
let controller = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.WallpaperPreview_PreviewInDayMode : strongSelf.presentationData.strings.WallpaperPreview_PreviewInNightMode, style: .customBlur(UIColor(rgb: 0x333333, alpha: 0.33)), icon: nil, location: .point(frame.offsetBy(dx: 1.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
let controller = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.WallpaperPreview_PreviewInDayMode : strongSelf.presentationData.strings.WallpaperPreview_PreviewInNightMode, style: .customBlur(UIColor(rgb: 0x333333, alpha: 0.45)), icon: nil, location: .point(frame.offsetBy(dx: 1.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
})
strongSelf.galleryController()?.present(controller, in: .current)

View File

@ -41,7 +41,7 @@ final class WallpaperLightButtonBackgroundNode: ASDisplayNode {
private let lightNode: ASDisplayNode
override init() {
self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.3), enableBlur: true, enableSaturation: false)
self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.45), enableBlur: true, enableSaturation: false)
self.overlayNode = ASDisplayNode()
self.overlayNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.75)
self.overlayNode.layer.compositingFilter = "overlayBlendMode"
@ -72,7 +72,7 @@ final class WallpaperOptionBackgroundNode: ASDisplayNode {
private let backgroundNode: NavigationBackgroundNode
init(enableSaturation: Bool = false) {
self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.3), enableBlur: true, enableSaturation: enableSaturation)
self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.45), enableBlur: true, enableSaturation: enableSaturation)
super.init()
@ -488,7 +488,7 @@ final class WallpaperSliderNode: ASDisplayNode {
self.value = value
self.valueChanged = valueChanged
self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.3), enableBlur: true, enableSaturation: false)
self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.45), enableBlur: true, enableSaturation: false)
self.foregroundNode = ASDisplayNode()
self.foregroundNode.clipsToBounds = true

View File

@ -915,6 +915,7 @@ public class Account {
public private(set) var pendingUpdateMessageManager: PendingUpdateMessageManager!
private(set) var messageMediaPreuploadManager: MessageMediaPreuploadManager!
private(set) var mediaReferenceRevalidationContext: MediaReferenceRevalidationContext!
public private(set) var pendingPeerMediaUploadManager: PendingPeerMediaUploadManager!
private var peerInputActivityManager: PeerInputActivityManager!
private var localInputActivityManager: PeerInputActivityManager!
private var accountPresenceManager: AccountPresenceManager!
@ -1029,6 +1030,7 @@ public class Account {
self.messageMediaPreuploadManager = MessageMediaPreuploadManager()
self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, accountPeerId: peerId, auxiliaryMethods: auxiliaryMethods, stateManager: self.stateManager, localInputActivityManager: self.localInputActivityManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.mediaReferenceRevalidationContext)
self.pendingUpdateMessageManager = PendingUpdateMessageManager(postbox: postbox, network: network, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext)
self.pendingPeerMediaUploadManager = PendingPeerMediaUploadManager(postbox: postbox, network: network, stateManager: self.stateManager, accountPeerId: self.peerId)
self.network.loggedOut = { [weak self] in
Logger.shared.log("Account", "network logged out")
@ -1138,13 +1140,15 @@ public class Account {
self.managedOperationsDisposable.add(managedPeerTimestampAttributeOperations(network: self.network, postbox: self.postbox).start())
self.managedOperationsDisposable.add(managedLocalTypingActivities(activities: self.localInputActivityManager.allActivities(), postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
let extractedExpr: [Signal<AccountRunningImportantTasks, NoError>] = [
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
self.pendingMessageManager.hasPendingMessages |> map { !$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] },
self.pendingUpdateMessageManager.updatingMessageMedia |> map { !$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] },
self.pendingPeerMediaUploadManager.uploadingPeerMedia |> map { !$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] },
self.accountPresenceManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] },
self.notificationAutolockReportManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] }
]
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = extractedExpr
let importantBackgroundOperationsRunning = combineLatest(queue: Queue(), importantBackgroundOperations)
|> map { values -> AccountRunningImportantTasks in
var result: AccountRunningImportantTasks = []

View File

@ -0,0 +1,403 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
public final class PeerMediaUploadingItem: Equatable {
public enum ProgressValue {
case progress(Float)
case done(Api.Updates)
}
public enum Error {
case generic
}
public enum PreviousState: Equatable {
case wallpaper(TelegramWallpaper?)
}
public enum Content: Equatable {
case wallpaper(TelegramWallpaper)
}
public let content: Content
public let messageId: EngineMessage.Id?
public let previousState: PreviousState?
public let progress: Float
init(content: Content, messageId: EngineMessage.Id?, previousState: PreviousState?, progress: Float) {
self.content = content
self.messageId = messageId
self.previousState = previousState
self.progress = progress
}
public static func ==(lhs: PeerMediaUploadingItem, rhs: PeerMediaUploadingItem) -> Bool {
if lhs.content != rhs.content {
return false
}
if lhs.messageId != rhs.messageId {
return false
}
if lhs.previousState != rhs.previousState {
return false
}
if lhs.progress != rhs.progress {
return false
}
return true
}
func withMessageId(_ messageId: EngineMessage.Id) -> PeerMediaUploadingItem {
return PeerMediaUploadingItem(content: self.content, messageId: messageId, previousState: self.previousState, progress: self.progress)
}
func withProgress(_ progress: Float) -> PeerMediaUploadingItem {
return PeerMediaUploadingItem(content: self.content, messageId: self.messageId, previousState: self.previousState, progress: progress)
}
func withPreviousState(_ previousState: PreviousState?) -> PeerMediaUploadingItem {
return PeerMediaUploadingItem(content: self.content, messageId: self.messageId, previousState: previousState, progress: self.progress)
}
}
private func uploadPeerMedia(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) -> Signal<PeerMediaUploadingItem.ProgressValue, PeerMediaUploadingItem.Error> {
switch content {
case let .wallpaper(wallpaper):
if case let .image(representations, settings) = wallpaper, let resource = representations.last?.resource as? LocalFileMediaResource {
return _internal_uploadWallpaper(postbox: postbox, network: network, resource: resource, settings: settings, forChat: true)
|> mapError { error -> PeerMediaUploadingItem.Error in
return .generic
}
|> mapToSignal { value -> Signal<PeerMediaUploadingItem.ProgressValue, PeerMediaUploadingItem.Error> in
switch value {
case let .progress(progress):
return .single(.progress(progress))
case let .complete(result):
if case let .file(file) = result {
postbox.mediaBox.copyResourceData(from: resource.id, to: file.file.resource.id, synchronous: true)
for representation in file.file.previewRepresentations {
postbox.mediaBox.copyResourceData(from: resource.id, to: representation.resource.id, synchronous: true)
}
}
return _internal_setChatWallpaper(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, wallpaper: result, applyUpdates: false)
|> castError(PeerMediaUploadingItem.Error.self)
|> map { updates -> PeerMediaUploadingItem.ProgressValue in
return .done(updates)
}
}
}
} else {
return _internal_setChatWallpaper(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, wallpaper: wallpaper, applyUpdates: false)
|> castError(PeerMediaUploadingItem.Error.self)
|> map { updates -> PeerMediaUploadingItem.ProgressValue in
return .done(updates)
}
}
}
}
private func generatePeerMediaMessage(network: Network, accountPeerId: EnginePeer.Id, transaction: Transaction, peerId: PeerId, content: PeerMediaUploadingItem.Content) -> StoreMessage {
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
var timestamp = Int32(network.context.globalTime())
switch peerId.namespace {
case Namespaces.Peer.CloudChannel, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudUser:
if let topIndex = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) {
timestamp = max(timestamp, topIndex.timestamp)
}
default:
break
}
var flags = StoreMessageFlags()
flags.insert(.Unsent)
flags.insert(.Sending)
var attributes: [MessageAttribute] = []
attributes.append(OutgoingMessageInfoAttribute(uniqueId: randomId, flags: [], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: []))
var media: [Media] = []
switch content {
case let .wallpaper(wallpaper):
media.append(TelegramMediaAction(action: .setChatWallpaper(wallpaper: wallpaper)))
}
return StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: accountPeerId, text: "", attributes: attributes, media: media)
}
private func preparePeerMediaUpload(transaction: Transaction, peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) -> PeerMediaUploadingItem.PreviousState? {
var previousState: PeerMediaUploadingItem.PreviousState?
switch content {
case let .wallpaper(wallpaper):
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in
if let cachedData = cachedData as? CachedUserData {
previousState = .wallpaper(cachedData.wallpaper)
return cachedData.withUpdatedWallpaper(wallpaper)
} else {
return cachedData
}
})
}
return previousState
}
private func cancelPeerMediaUpload(transaction: Transaction, peerId: EnginePeer.Id, previousState: PeerMediaUploadingItem.PreviousState?) {
guard let previousState = previousState else {
return
}
switch previousState {
case let .wallpaper(previousWallpaper):
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in
if let cachedData = cachedData as? CachedUserData {
return cachedData.withUpdatedWallpaper(previousWallpaper)
} else {
return cachedData
}
})
}
}
private final class PendingPeerMediaUploadContext {
var value: PeerMediaUploadingItem
let disposable = MetaDisposable()
init(value: PeerMediaUploadingItem) {
self.value = value
}
}
private final class PendingPeerMediaUploadManagerImpl {
let queue: Queue
let postbox: Postbox
let network: Network
let stateManager: AccountStateManager
let accountPeerId: EnginePeer.Id
private var uploadingPeerMediaValue: [EnginePeer.Id: PeerMediaUploadingItem] = [:] {
didSet {
if self.uploadingPeerMediaValue != oldValue {
self.uploadingPeerMediaPromise.set(.single(self.uploadingPeerMediaValue))
}
}
}
private let uploadingPeerMediaPromise = Promise<[EnginePeer.Id: PeerMediaUploadingItem]>()
fileprivate var uploadingPeerMedia: Signal<[EnginePeer.Id: PeerMediaUploadingItem], NoError> {
return self.uploadingPeerMediaPromise.get()
}
private var contexts: [PeerId: PendingPeerMediaUploadContext] = [:]
init(queue: Queue, postbox: Postbox, network: Network, stateManager: AccountStateManager, accountPeerId: EnginePeer.Id) {
self.queue = queue
self.postbox = postbox
self.network = network
self.stateManager = stateManager
self.accountPeerId = accountPeerId
self.uploadingPeerMediaPromise.set(.single(self.uploadingPeerMediaValue))
}
deinit {
for (_, context) in self.contexts {
context.disposable.dispose()
}
}
private func updateValues() {
self.uploadingPeerMediaValue = self.contexts.mapValues { context in
return context.value
}
}
func add(peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) {
if let context = self.contexts[peerId] {
self.contexts.removeValue(forKey: peerId)
context.disposable.dispose()
}
let postbox = self.postbox
let network = self.network
let stateManager = self.stateManager
let accountPeerId = self.accountPeerId
let queue = self.queue
let context = PendingPeerMediaUploadContext(value: PeerMediaUploadingItem(content: content, messageId: nil, previousState: nil, progress: 0.0))
self.contexts[peerId] = context
context.disposable.set(
(self.postbox.transaction({ transaction -> (EngineMessage.Id, PeerMediaUploadingItem.PreviousState?)? in
let storeMessage = generatePeerMediaMessage(network: network, accountPeerId: accountPeerId, transaction: transaction, peerId: peerId, content: content)
let globallyUniqueIdToMessageId = transaction.addMessages([storeMessage], location: .Random)
guard let globallyUniqueId = storeMessage.globallyUniqueId, let messageId = globallyUniqueIdToMessageId[globallyUniqueId] else {
return nil
}
let previousState = preparePeerMediaUpload(transaction: transaction, peerId: peerId, content: content)
return (messageId, previousState)
})
|> deliverOn(queue)).start(next: { [weak self, weak context] messageIdAndPreviousState in
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
guard let (messageId, previousState) = messageIdAndPreviousState else {
strongSelf.contexts.removeValue(forKey: peerId)
context.disposable.dispose()
strongSelf.updateValues()
return
}
context.value = context.value.withMessageId(messageId).withPreviousState(previousState)
strongSelf.updateValues()
context.disposable.set((uploadPeerMedia(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, content: content)
|> deliverOn(queue)).start(next: { [weak self, weak context] value in
queue.async {
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
switch value {
case let .done(result):
context.disposable.set(
(postbox.transaction({ transaction -> Message? in
return transaction.getMessage(messageId)
})
|> deliverOn(queue)
).start(next: { [weak self, weak context] message in
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
guard let message = message else {
strongSelf.contexts.removeValue(forKey: peerId)
context.disposable.dispose()
strongSelf.updateValues()
return
}
context.disposable.set(
(applyUpdateMessage(
postbox: postbox,
stateManager: stateManager,
message: message,
cacheReferenceKey: nil,
result: result,
accountPeerId: accountPeerId
)
|> deliverOn(queue)).start(completed: { [weak self, weak context] in
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
strongSelf.contexts.removeValue(forKey: peerId)
context.disposable.dispose()
strongSelf.updateValues()
}
})
)
}
})
)
strongSelf.updateValues()
case let .progress(progress):
context.value = context.value.withProgress(progress)
strongSelf.updateValues()
}
}
}
}, error: { [weak self, weak context] error in
queue.async {
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
strongSelf.contexts.removeValue(forKey: peerId)
context.disposable.dispose()
strongSelf.updateValues()
}
}
}))
}
})
)
}
func cancel(peerId: EnginePeer.Id) {
if let context = self.contexts[peerId] {
self.contexts.removeValue(forKey: peerId)
if let messageId = context.value.messageId {
context.disposable.set(self.postbox.transaction({ transaction in
cancelPeerMediaUpload(transaction: transaction, peerId: peerId, previousState: context.value.previousState)
transaction.deleteMessages([messageId], forEachMedia: nil)
}).start())
} else {
context.disposable.dispose()
}
self.updateValues()
}
}
func uploadProgress(messageId: EngineMessage.Id) -> Signal<Float?, NoError> {
return self.uploadingPeerMedia
|> map { uploadingPeerMedia in
if let item = uploadingPeerMedia[messageId.peerId], item.messageId == messageId {
return item.progress
} else {
return nil
}
}
|> distinctUntilChanged
}
}
public final class PendingPeerMediaUploadManager {
private let queue = Queue()
private let impl: QueueLocalObject<PendingPeerMediaUploadManagerImpl>
public var uploadingPeerMedia: Signal<[EnginePeer.Id: PeerMediaUploadingItem], NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.uploadingPeerMedia.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
init(postbox: Postbox, network: Network, stateManager: AccountStateManager, accountPeerId: EnginePeer.Id) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return PendingPeerMediaUploadManagerImpl(queue: queue, postbox: postbox, network: network, stateManager: stateManager, accountPeerId: accountPeerId)
})
}
public func add(peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) {
self.impl.with { impl in
impl.add(peerId: peerId, content: content)
}
}
public func cancel(peerId: EnginePeer.Id) {
self.impl.with { impl in
impl.cancel(peerId: peerId)
}
}
public func uploadProgress(messageId: EngineMessage.Id) -> Signal<Float?, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.uploadProgress(messageId: messageId).start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
}

View File

@ -118,14 +118,14 @@ func managedChatThemesUpdates(accountManager: AccountManager<TelegramAccountMana
return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
func _internal_setChatWallpaper(account: Account, peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal<Void, NoError> {
return account.postbox.loadedPeerWithId(peerId)
func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, wallpaper: TelegramWallpaper?, applyUpdates: Bool = true) -> Signal<Api.Updates, NoError> {
return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer in
guard let inputPeer = apiInputPeer(peer) else {
return .complete()
}
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
return postbox.transaction { transaction -> Signal<Api.Updates, NoError> in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedUserData {
return current.withUpdatedWallpaper(wallpaper)
@ -144,13 +144,15 @@ func _internal_setChatWallpaper(account: Account, peerId: PeerId, wallpaper: Tel
inputWallpaper = inputWallpaperAndInputSettings.0
inputSettings = inputWallpaperAndInputSettings.1
}
return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper, settings: inputSettings, id: nil), automaticFloodWait: false)
return network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper, settings: inputSettings, id: nil), automaticFloodWait: false)
|> `catch` { error in
return .complete()
}
|> mapToSignal { updates -> Signal<Void, NoError> in
account.stateManager.addUpdates(updates)
return .complete()
|> mapToSignal { updates -> Signal<Api.Updates, NoError> in
if applyUpdates {
stateManager.addUpdates(updates)
}
return .single(updates)
}
} |> switchToLatest
}

View File

@ -17,8 +17,9 @@ public extension TelegramEngine {
return _internal_setChatTheme(account: self.account, peerId: peerId, emoticon: emoticon)
}
public func setChatWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal<Void, NoError> {
return _internal_setChatWallpaper(account: self.account, peerId: peerId, wallpaper: wallpaper)
public func setChatWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal<Never, NoError> {
return _internal_setChatWallpaper(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, wallpaper: wallpaper)
|> ignoreValues
}
public func setExistingChatWallpaper(messageId: MessageId, settings: WallpaperSettings?) -> Signal<Void, SetExistingChatWallpaperError> {

View File

@ -119,7 +119,11 @@ private func uploadedWallpaper(postbox: Postbox, network: Network, resource: Med
}
public func uploadWallpaper(account: Account, resource: MediaResource, mimeType: String = "image/jpeg", settings: WallpaperSettings, forChat: Bool) -> Signal<UploadWallpaperStatus, UploadWallpaperError> {
return uploadedWallpaper(postbox: account.postbox, network: account.network, resource: resource)
return _internal_uploadWallpaper(postbox: account.postbox, network: account.network, resource: resource, settings: settings, forChat: forChat)
}
func _internal_uploadWallpaper(postbox: Postbox, network: Network, resource: MediaResource, mimeType: String = "image/jpeg", settings: WallpaperSettings, forChat: Bool) -> Signal<UploadWallpaperStatus, UploadWallpaperError> {
return uploadedWallpaper(postbox: postbox, network: network, resource: resource)
|> mapError { _ -> UploadWallpaperError in }
|> mapToSignal { result -> Signal<(UploadWallpaperStatus, MediaResource?), UploadWallpaperError> in
switch result.content {
@ -134,11 +138,11 @@ public func uploadWallpaper(account: Account, resource: MediaResource, mimeType:
if forChat {
flags |= 1 << 0
}
return account.network.request(Api.functions.account.uploadWallPaper(flags: flags, file: file, mimeType: mimeType, settings: apiWallpaperSettings(settings)))
|> mapError { _ in return UploadWallpaperError.generic }
|> map { wallpaper -> (UploadWallpaperStatus, MediaResource?) in
return (.complete(TelegramWallpaper(apiWallpaper: wallpaper)), result.resource)
}
return network.request(Api.functions.account.uploadWallPaper(flags: flags, file: file, mimeType: mimeType, settings: apiWallpaperSettings(settings)))
|> mapError { _ in return UploadWallpaperError.generic }
|> map { wallpaper -> (UploadWallpaperStatus, MediaResource?) in
return (.complete(TelegramWallpaper(apiWallpaper: wallpaper)), result.resource)
}
default:
return .fail(.generic)
}

View File

@ -18655,7 +18655,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, let peerId else {
return
}
if canResetWallpaper {
if canResetWallpaper && emoticon != nil {
let _ = context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil).start()
}
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon ?? "", nil)))

View File

@ -733,7 +733,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} else {
unboundSize = CGSize(width: 54.0, height: 54.0)
}
case .themeSettings:
case .themeSettings, .image:
unboundSize = CGSize(width: 160.0, height: 240.0).fitted(CGSize(width: 240.0, height: 240.0))
case .color, .gradient:
unboundSize = CGSize(width: 128.0, height: 128.0)
@ -1116,6 +1116,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), representations: representations, alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true)
}
}
case let .image(representations):
return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: nil, representations: representations.map({ ImageRepresentationWithReference(representation: $0, reference: .standalone(resource: $0.resource)) }), alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true)
case let .themeSettings(settings):
return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .settings(settings))
case let .color(color):
@ -1182,7 +1184,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|> map { resourceStatus -> (MediaResourceStatus, MediaResourceStatus?) in
return (resourceStatus, nil)
}
case .themeSettings, .color, .gradient:
case .themeSettings, .color, .gradient, .image:
updatedStatusSignal = .single((.Local, nil))
}
}

View File

@ -15,12 +15,16 @@ import WallpaperBackgroundNode
import PhotoResources
import WallpaperResources
import Markdown
import RadialStatusNode
class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
private var mediaBackgroundContent: WallpaperBubbleBackgroundNode?
private let mediaBackgroundNode: NavigationBackgroundNode
private let subtitleNode: TextNode
private let imageNode: TransformImageNode
private var statusOverlayNode: ASDisplayNode
private var statusNode: RadialStatusNode
private let buttonNode: HighlightTrackingButtonNode
private let buttonTitleNode: TextNode
@ -28,6 +32,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
private var absoluteRect: (CGRect, CGSize)?
private let fetchDisposable = MetaDisposable()
private let statusDisposable = MetaDisposable()
required init() {
self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear)
@ -48,6 +53,15 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
self.buttonTitleNode.isUserInteractionEnabled = false
self.buttonTitleNode.displaysAsynchronously = false
self.statusOverlayNode = ASDisplayNode()
self.statusOverlayNode.alpha = 0.0
self.statusOverlayNode.clipsToBounds = true
self.statusOverlayNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4)
self.statusOverlayNode.cornerRadius = 50.0
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6))
self.statusNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.mediaBackgroundNode)
@ -57,6 +71,9 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
self.addSubnode(self.buttonNode)
self.addSubnode(self.buttonTitleNode)
self.addSubnode(self.statusOverlayNode)
self.statusOverlayNode.addSubnode(self.statusNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
@ -82,6 +99,25 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
deinit {
self.fetchDisposable.dispose()
self.statusDisposable.dispose()
}
override func didLoad() {
super.didLoad()
if #available(iOS 13.0, *) {
self.statusOverlayNode.layer.cornerCurve = .circular
}
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.progressPressed))
self.statusOverlayNode.view.addGestureRecognizer(tapGestureRecognizer)
}
@objc private func progressPressed() {
guard let item = self.item else {
return
}
item.context.account.pendingPeerMediaUploadManager.cancel(peerId: item.message.id.peerId)
}
override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
@ -134,6 +170,18 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
}
let _ = item.controllerInteraction.openMessage(item.message, .default)
}
private func updateProgress(_ progress: Float?) {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
if let progress {
let progressValue = CGFloat(max(0.027, progress))
self.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: progressValue, cancelEnabled: true, animateRotation: true))
transition.updateAlpha(node: self.statusOverlayNode, alpha: 1.0)
} else {
self.statusNode.transitionToState(.none)
transition.updateAlpha(node: self.statusOverlayNode, alpha: 0.0)
}
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeImageLayout = self.imageNode.asyncLayout()
@ -223,6 +271,11 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
}
updateImageSignal = wallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file), representations: representations, alwaysShowThumbnailFirst: true, thumbnail: true, autoFetchFullSize: true)
}
case let .image(representations):
if let dimensions = representations.last?.dimensions.cgSize {
imageSize = dimensions.aspectFilled(boundingSize)
}
updateImageSignal = wallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, fileReference: nil, representations: representations.map({ ImageRepresentationWithReference(representation: $0, reference: .standalone(resource: $0.resource)) }), alwaysShowThumbnailFirst: true, thumbnail: true, autoFetchFullSize: true)
case let .color(color):
updateImageSignal = solidColorImage(color)
case let .gradient(colors, rotation):
@ -240,6 +293,24 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.imageNode.frame = imageFrame
}
let radialStatusSize: CGFloat = 50.0
strongSelf.statusOverlayNode.frame = imageFrame
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.width - radialStatusSize) / 2.0), y: floor((imageFrame.height - radialStatusSize) / 2.0)), size: CGSize(width: radialStatusSize, height: radialStatusSize))
if mediaUpdated {
if item.message.id.namespace == Namespaces.Message.Local {
strongSelf.statusDisposable.set((item.context.account.pendingPeerMediaUploadManager.uploadProgress(messageId: item.message.id)
|> deliverOnMainQueue).start(next: { [weak self] progress in
if let strongSelf = self {
strongSelf.updateProgress(progress)
}
}))
} else {
strongSelf.statusDisposable.set(nil)
strongSelf.updateProgress(nil)
}
}
let mediaBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - width) / 2.0), y: 0.0), size: backgroundSize)
strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame
@ -299,7 +370,9 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.mediaBackgroundNode.frame.contains(point) {
if self.statusOverlayNode.alpha > 0.0 {
return .none
} else if self.mediaBackgroundNode.frame.contains(point) {
return .openMessage
} else {
return .none

View File

@ -5,6 +5,7 @@ import TelegramCore
enum WallpaperPreviewMediaContent: Equatable {
case file(file: TelegramMediaFile, colors: [UInt32], rotation: Int32?, intensity: Int32?, Bool, Bool)
case image(representations: [TelegramMediaImageRepresentation])
case color(UIColor)
case gradient([UInt32], Int32?)
case themeSettings(TelegramThemeSettings)
@ -55,6 +56,8 @@ extension WallpaperPreviewMedia {
self.init(content: .gradient(gradient.colors, gradient.settings.rotation))
case let .file(file):
self.init(content: .file(file: file.file, colors: file.settings.colors, rotation: file.settings.rotation, intensity: file.settings.intensity, false, false))
case let .image(representations, _):
self.init(content: .image(representations: representations))
default:
return nil
}