From 9b7562c7bce29849204ae36200c88e3c60a9b855 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 24 Mar 2023 21:43:27 +0400 Subject: [PATCH 01/57] Temp --- .../Sources/OngoingCallContext.swift | 267 +++++++++++++++++- .../OngoingCallThreadLocalContext.h | 11 +- .../Sources/OngoingCallThreadLocalContext.mm | 44 ++- 3 files changed, 304 insertions(+), 18 deletions(-) diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index ddd3f5e954e..92a1e664d1d 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -933,22 +933,58 @@ public final class OngoingCallContext { } } - let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: Data(), key: key, isOutgoing: isOutgoing, connections: filteredConnections, maxLayer: maxLayer, allowP2P: allowP2P, allowTCP: enableTCP, enableStunMarking: enableStunMarking, logPath: logPath, statsLogPath: tempStatsLogPath, sendSignalingData: { [weak callSessionManager] data in - queue.async { - guard let strongSelf = self else { - return + var directConnection: OngoingCallDirectConnection? + #if DEBUG + if #available(iOS 12.0, *) { + for connection in filteredConnections { + if connection.username == "reflector" && connection.reflectorId == 1 && !connection.hasTcp && connection.hasTurn { + directConnection = CallDirectConnectionImpl(host: connection.ip, port: Int(connection.port), peerTag: dataWithHexString(connection.password)) + break } - if let signalingConnectionManager = strongSelf.signalingConnectionManager { - signalingConnectionManager.with { impl in - impl.send(payloadData: data) + } + } + #else + directConnection = nil + #endif + + let context = OngoingCallThreadLocalContextWebrtc( + version: version, + queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), + proxy: voipProxyServer, + networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), + dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), + derivedState: Data(), + key: key, + isOutgoing: isOutgoing, + connections: filteredConnections, + maxLayer: maxLayer, + allowP2P: allowP2P, + allowTCP: enableTCP, + enableStunMarking: enableStunMarking, + logPath: logPath, + statsLogPath: tempStatsLogPath, + sendSignalingData: { [weak callSessionManager] data in + queue.async { + guard let strongSelf = self else { + return + } + if let signalingConnectionManager = strongSelf.signalingConnectionManager { + signalingConnectionManager.with { impl in + impl.send(payloadData: data) + } + } + + if let callSessionManager = callSessionManager { + callSessionManager.sendSignalingData(internalId: internalId, data: data) } } - - if let callSessionManager = callSessionManager { - callSessionManager.sendSignalingData(internalId: internalId, data: data) - } - } - }, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "", audioDevice: audioDevice?.impl) + }, + videoCapturer: video?.impl, + preferredVideoCodec: preferredVideoCodec, + audioInputDeviceId: "", + audioDevice: audioDevice?.impl, + directConnection: directConnection + ) strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in @@ -1287,12 +1323,204 @@ public final class OngoingCallContext { } } -private protocol CallSignalingConnection { +private protocol CallSignalingConnection: AnyObject { func start() func stop() func send(payloadData: Data) } +@available(iOS 13.0, *) +private class CustomWrapperProtocol: NWProtocolFramerImplementation { + static var label: String = "CustomWrapperProtocol" + + static let definition = NWProtocolFramer.Definition(implementation: CustomWrapperProtocol.self) + + required init(framer: NWProtocolFramer.Instance) { + + } + + func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { + return .ready + } + + func handleInput(framer: NWProtocolFramer.Instance) -> Int { + preconditionFailure() + } + + func handleOutput(framer: NWProtocolFramer.Instance, message: NWProtocolFramer.Message, messageLength: Int, isComplete: Bool) { + preconditionFailure() + } + + func wakeup(framer: NWProtocolFramer.Instance) { + } + + func stop(framer: NWProtocolFramer.Instance) -> Bool { + return true + } + + func cleanup(framer: NWProtocolFramer.Instance) { + } +} + +@available(iOS 12.0, *) +private final class CallDirectConnectionImpl: NSObject, OngoingCallDirectConnection { + private final class Impl { + private let queue: Queue + private let peerTag: Data + + private var connection: NWConnection? + + var incomingDataHandler: ((Data) -> Void)? + + init(queue: Queue, host: String, port: Int, peerTag: Data) { + self.queue = queue + + var peerTag = peerTag + peerTag.withUnsafeMutableBytes { buffer in + let bytes = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self) + for i in (buffer.count - 4) ..< buffer.count { + bytes.advanced(by: i).pointee = 1 + } + } + self.peerTag = peerTag + + if let port = NWEndpoint.Port(rawValue: UInt16(clamping: port)) { + self.connection = NWConnection(host: NWEndpoint.Host(host), port: port, using: .udp) + } + + self.connection?.stateUpdateHandler = { newState in + switch newState { + case .ready: + print("CallDirectConnection: State: Ready") + case .setup: + print("CallDirectConnection: State: Setup") + case .cancelled: + print("CallDirectConnection: State: Cancelled") + case .preparing: + print("CallDirectConnection: State: Preparing") + case let .waiting(error): + print("CallDirectConnection: State: Waiting (\(error))") + case let .failed(error): + print("CallDirectConnection: State: Error (\(error))") + @unknown default: + print("CallDirectConnection: State: Unknown") + } + } + + self.connection?.start(queue: self.queue.queue) + self.receive() + } + + deinit { + + } + + private func receive() { + let queue = self.queue + self.connection?.receiveMessage(completion: { [weak self] data, _, _, error in + assert(queue.isCurrent()) + + guard let self else { + return + } + + if let data { + if data.count >= 16 { + var unwrappedData = Data(count: data.count - 16) + unwrappedData.withUnsafeMutableBytes { destBuffer -> Void in + data.withUnsafeBytes { sourceBuffer -> Void in + sourceBuffer.copyBytes(to: destBuffer, from: 16 ..< sourceBuffer.count) + } + } + + self.incomingDataHandler?(unwrappedData) + } else { + print("Invalid data size") + } + } + if error == nil { + self.receive() + } + }) + } + + func send(data: Data) { + var wrappedData = Data() + wrappedData.append(self.peerTag) + wrappedData.append(data) + + self.connection?.send(content: wrappedData, completion: .contentProcessed({ error in + if let error { + print("Send error: \(error)") + } + })) + } + } + + private static let sharedQueue = Queue(name: "CallDirectConnectionImpl") + + private let queue: Queue + private let impl: QueueLocalObject + + private let incomingDataHandlers = Atomic Void>>(value: Bag()) + + init(host: String, port: Int, peerTag: Data) { + let queue = CallDirectConnectionImpl.sharedQueue + self.queue = queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue, host: host, port: port, peerTag: peerTag) + }) + + let incomingDataHandlers = self.incomingDataHandlers + self.impl.with { [weak incomingDataHandlers] impl in + impl.incomingDataHandler = { data in + guard let incomingDataHandlers else { + return + } + for f in incomingDataHandlers.with({ return $0.copyItems() }) { + f(data) + } + } + } + } + + func add(onIncomingPacket addOnIncomingPacket: @escaping (Data) -> Void) -> Data { + var token = self.incomingDataHandlers.with { bag -> Int32 in + return Int32(bag.add(addOnIncomingPacket)) + } + return withUnsafeBytes(of: &token, { buffer -> Data in + let bytes = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self) + return Data(bytes: bytes, count: 4) + }) + } + + func remove(onIncomingPacket token: Data) { + if token.count != 4 { + return + } + + var tokenValue: Int32 = 0 + withUnsafeMutableBytes(of: &tokenValue, { tokenBuffer in + let tokenBytes = tokenBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self) + + token.withUnsafeBytes { sourceBuffer in + let sourceBytes = sourceBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self) + memcpy(tokenBytes, sourceBytes, 4) + } + }) + + self.incomingDataHandlers.with { bag in + bag.remove(Int(tokenValue)) + } + } + + func sendPacket(_ packet: Data) { + self.impl.with { impl in + impl.send(data: packet) + } + } +} + @available(iOS 12.0, *) private final class CallSignalingConnectionImpl: CallSignalingConnection { private let queue: Queue @@ -1316,7 +1544,18 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection { self.peerTag = peerTag self.dataReceived = dataReceived self.isClosed = isClosed + + #if DEBUG + if #available(iOS 15.0, *) { + let parameters = NWParameters.quic(alpn: ["tgcalls"]) + parameters.defaultProtocolStack.internetProtocol = NWProtocolFramer.Options(definition: CustomWrapperProtocol.definition) + self.connection = NWConnection(host: self.host, port: self.port, using: parameters) + } else { + preconditionFailure() + } + #else self.connection = NWConnection(host: self.host, port: self.port, using: .tcp) + #endif self.connection.stateUpdateHandler = { [weak self] state in queue.async { diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 734f6001617..daf08754987 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -215,6 +215,14 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { @end +@protocol OngoingCallDirectConnection + +- (NSData * _Nonnull)addOnIncomingPacket:(void (^_Nonnull)(NSData * _Nonnull))addOnIncomingPacket; +- (void)removeOnIncomingPacket:(NSData * _Nonnull)token; +- (void)sendPacket:(NSData * _Nonnull)packet; + +@end + @interface OngoingCallThreadLocalContextWebrtc : NSObject + (void)logMessage:(NSString * _Nonnull)string; @@ -245,7 +253,8 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId - audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice; + audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice + directConnection:(id _Nullable)directConnection; - (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 907e01cd87c..110536a4edd 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -134,7 +134,7 @@ - (instancetype _Nonnull)initWithDisableRecording:(bool)disableRecording { self = [super init]; if (self != nil) { _audioDeviceModule.reset(new tgcalls::ThreadLocalObject(tgcalls::StaticThreads::getThreads()->getWorkerThread(), [disableRecording]() mutable { - return (tgcalls::SharedAudioDeviceModule *)(new SharedAudioDeviceModuleImpl(disableRecording)); + return std::static_pointer_cast(std::make_shared(disableRecording)); })); } return self; @@ -535,6 +535,37 @@ void OnFrame(const webrtc::VideoFrame& nativeVideoFrame) override { void (^_frameReceived)(webrtc::VideoFrame const &); }; +class DirectConnectionChannelImpl : public tgcalls::DirectConnectionChannel { +public: + DirectConnectionChannelImpl(id _Nonnull impl) { + _impl = impl; + } + + virtual ~DirectConnectionChannelImpl() { + } + + virtual std::vector addOnIncomingPacket(std::function>)> &&handler) override { + __block auto localHandler = std::move(handler); + + NSData *token = [_impl addOnIncomingPacket:^(NSData * _Nonnull data) { + std::shared_ptr> mappedData = std::make_shared>((uint8_t const *)data.bytes, (uint8_t const *)data.bytes + data.length); + localHandler(mappedData); + }]; + return std::vector((uint8_t * const)token.bytes, (uint8_t * const)token.bytes + token.length); + } + + virtual void removeOnIncomingPacket(std::vector &token) override { + [_impl removeOnIncomingPacket:[[NSData alloc] initWithBytes:token.data() length:token.size()]]; + } + + virtual void sendPacket(std::unique_ptr> &&packet) override { + [_impl sendPacket:[[NSData alloc] initWithBytes:packet->data() length:packet->size()]]; + } + +private: + id _impl; +}; + } @interface GroupCallVideoSink : NSObject { @@ -1024,7 +1055,8 @@ - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id< sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId - audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice { + audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice + directConnection:(id _Nullable)directConnection { self = [super init]; if (self != nil) { _version = version; @@ -1149,6 +1181,11 @@ - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id< audioDeviceModule = [_audioDevice getAudioDeviceModule]; } + std::shared_ptr directConnectionChannel; + if (directConnection) { + directConnectionChannel = std::static_pointer_cast(std::make_shared(directConnection)); + } + __weak OngoingCallThreadLocalContextWebrtc *weakSelf = self; _tgVoip = tgcalls::Meta::Create([version UTF8String], (tgcalls::Descriptor){ .version = [version UTF8String], @@ -1288,7 +1325,8 @@ - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id< }]; return resultModule; } - } + }, + .directConnectionChannel = directConnectionChannel }); _state = OngoingCallStateInitializing; _signalBars = 4; From fc5fe12730f4c6720b3341b9e032a0a4bf58a535 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 27 Mar 2023 21:19:39 +0400 Subject: [PATCH 02/57] Update tgcalls --- submodules/TgVoipWebrtc/tgcalls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 35d5d408cee..a04d4e182a7 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 35d5d408cee8c69b61a32bc96dbfccc37980a5ae +Subproject commit a04d4e182a77977b30b82f648a589d77b63ce74c From 184a02baaa4e036e47cc8e74812bacc243e82e02 Mon Sep 17 00:00:00 2001 From: Mike Renoir <> Date: Thu, 6 Apr 2023 15:37:27 +0400 Subject: [PATCH 03/57] update cached data after service messages for chat background --- .../State/AccountStateManagementUtils.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 511563eefc0..e62c9497fe8 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -3405,6 +3405,20 @@ func replayFinalState( for (space, _) in holesAtHistoryStart { transaction.removeHole(peerId: chatPeerId, threadId: nil, namespace: Namespaces.Message.Cloud, space: space, range: 1 ... id.id) } + case let .setChatWallpaper(wallpaper): + transaction.updatePeerCachedData(peerIds: [message.id.peerId], update: { peerId, current in + var current = current + if current == nil { + if peerId.namespace == Namespaces.Peer.CloudUser { + current = CachedUserData() + } + } + if let cachedData = current as? CachedUserData { + return cachedData.withUpdatedWallpaper(wallpaper) + } else { + return current + } + }) default: break } From 86d12b5624ea425dd026df70b1ad53e9b0bb1f12 Mon Sep 17 00:00:00 2001 From: Mike Renoir <> Date: Thu, 6 Apr 2023 16:39:58 +0400 Subject: [PATCH 04/57] fix apply action --- .../State/AccountStateManagementUtils.swift | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index e62c9497fe8..b2b16fb8766 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -3406,19 +3406,21 @@ func replayFinalState( transaction.removeHole(peerId: chatPeerId, threadId: nil, namespace: Namespaces.Message.Cloud, space: space, range: 1 ... id.id) } case let .setChatWallpaper(wallpaper): - transaction.updatePeerCachedData(peerIds: [message.id.peerId], update: { peerId, current in - var current = current - if current == nil { - if peerId.namespace == Namespaces.Peer.CloudUser { - current = CachedUserData() - } - } - if let cachedData = current as? CachedUserData { - return cachedData.withUpdatedWallpaper(wallpaper) - } else { - return current - } - }) + if chatPeerId == accountPeerId { + transaction.updatePeerCachedData(peerIds: [message.id.peerId], update: { peerId, current in + var current = current + if current == nil { + if peerId.namespace == Namespaces.Peer.CloudUser { + current = CachedUserData() + } + } + if let cachedData = current as? CachedUserData { + return cachedData.withUpdatedWallpaper(wallpaper) + } else { + return current + } + }) + } default: break } From 858e6068646444e7a5d7c2815141ceedac108ec5 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 6 Apr 2023 17:06:28 +0400 Subject: [PATCH 05/57] Chat wallpaper improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 7 + .../Sources/AttachmentController.swift | 4 +- .../Sources/AttachmentPanel.swift | 6 +- .../Sources/MediaAssetsContext.swift | 13 +- .../Sources/MediaPickerScreen.swift | 50 ++++- .../Sources/MediaPickerTitleView.swift | 2 +- submodules/SettingsUI/BUILD | 1 + .../Reactions/ReactionChatPreviewItem.swift | 5 +- .../Themes/ThemeColorsGridController.swift | 189 ++++++++++++++---- .../ThemeColorsGridControllerNode.swift | 121 +++++++---- .../Sources/Themes/ThemeGridController.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 19 +- 12 files changed, 309 insertions(+), 110 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 4f21a5b6a05..8c6579dc491 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9151,3 +9151,10 @@ Sorry for the inconvenience."; "Conversation.Theme.SetPhotoWallpaper" = "Choose Background from Photos"; "Conversation.Theme.SetColorWallpaper" = "Choose Color as a Background"; "Conversation.Theme.OtherOptions" = "Other Options..."; + +"Conversation.Theme.ChooseWallpaperTitle" = "Choose Background"; +"Conversation.Theme.ResetWallpaper" = "Reset to Default Background"; +"Conversation.Theme.ChooseColorTitle" = "Set a Color"; +"Conversation.Theme.SetCustomColor" = "Set Custom"; + +"Appearance.ShowNextMediaOnTap" = "Show Next Media on Tap"; diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 552aefab1ce..504bcf075ae 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -177,7 +177,7 @@ private func generateMaskImage() -> UIImage? { public class AttachmentController: ViewController { private let context: AccountContext private let updatedPresentationData: (initial: PresentationData, signal: Signal)? - private let chatLocation: ChatLocation + private let chatLocation: ChatLocation? private let buttons: [AttachmentButtonType] private let initialButton: AttachmentButtonType private let fromMenu: Bool @@ -888,7 +888,7 @@ public class AttachmentController: ViewController { public var getSourceRect: (() -> CGRect?)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, hasTextInput: Bool = true, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView? = { return nil}) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation?, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, hasTextInput: Bool = true, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView? = { return nil}) { self.context = context self.updatedPresentationData = updatedPresentationData self.chatLocation = chatLocation diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 5e7cf29c0e8..c6062f9f638 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -758,13 +758,13 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { var mainButtonPressed: () -> Void = { } - init(context: AccountContext, chatLocation: ChatLocation, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { + init(context: AccountContext, chatLocation: ChatLocation?, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { self.context = context self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.makeEntityInputView = makeEntityInputView - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil) self.containerNode = ASDisplayNode() self.containerNode.clipsToBounds = true @@ -936,7 +936,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { return } textInputPanelNode.loadTextInputNodeIfNeeded() - guard let textInputNode = textInputPanelNode.textInputNode, let peerId = chatLocation.peerId else { + guard let textInputNode = textInputPanelNode.textInputNode, let peerId = chatLocation?.peerId else { return } diff --git a/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift b/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift index 113bf267e6b..aebaf1f84dc 100644 --- a/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift +++ b/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift @@ -5,12 +5,16 @@ import Photos import AVFoundation class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver { + private let assetType: PHAssetMediaType? + private var registeredChangeObserver = false private let changeSink = ValuePipe() private let mediaAccessSink = ValuePipe() private let cameraAccessSink = ValuePipe() - override init() { + init(assetType: PHAssetMediaType?) { + self.assetType = assetType + super.init() if PHPhotoLibrary.authorizationStatus() == .authorized { @@ -30,7 +34,12 @@ class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver { } func fetchAssets(_ collection: PHAssetCollection) -> Signal, NoError> { - let initialFetchResult = PHAsset.fetchAssets(in: collection, options: nil) + let options = PHFetchOptions() + if let assetType = self.assetType { + options.predicate = NSPredicate(format: "mediaType = %d", assetType.rawValue) + } + + let initialFetchResult = PHAsset.fetchAssets(in: collection, options: options) let fetchResult = Atomic>(value: initialFetchResult) return .single(initialFetchResult) |> then( diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index eb9d6215d54..7da26bf2711 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -128,7 +128,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } } - case assets(PHAssetCollection?, Bool) + public enum AssetsMode: Equatable { + case `default` + case wallpaper + } + + case assets(PHAssetCollection?, AssetsMode) case media([Media]) } @@ -238,7 +243,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.controller = controller self.presentationData = controller.presentationData - let mediaAssetsContext = MediaAssetsContext() + var assetType: PHAssetMediaType? + if case let .assets(_, mode) = controller.subject, case .wallpaper = mode { + assetType = .image + } + let mediaAssetsContext = MediaAssetsContext(assetType: assetType) self.mediaAssetsContext = mediaAssetsContext self.containerNode = ASDisplayNode() @@ -253,7 +262,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { super.init() - if case .assets(nil, false) = controller.subject { + if case .assets(nil, .default) = controller.subject { } else { self.preloadPromise.set(false) } @@ -454,7 +463,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } }) - if let controller = self.controller, case .assets(nil, false) = controller.subject { + if let controller = self.controller, case .assets(nil, .default) = controller.subject { let enableAnimations = self.controller?.context.sharedContext.energyUsageSettings.fullTranslucency ?? true let cameraView = TGAttachmentCameraView(forSelfPortrait: false, videoModeByDefault: controller.bannedSendPhotos != nil && controller.bannedSendVideos == nil)! @@ -556,7 +565,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { var updateLayout = false var selectable = true - if case let .assets(_, isStandalone) = controller.subject, isStandalone { + if case let .assets(_, mode) = controller.subject, mode != .default { selectable = false } @@ -1247,8 +1256,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) - if case let .assets(collection, _) = subject, let collection = collection { - self.titleView.title = collection.localizedTitle ?? presentationData.strings.Attachment_Gallery + if case let .assets(collection, mode) = subject { + if let collection = collection { + self.titleView.title = collection.localizedTitle ?? presentationData.strings.Attachment_Gallery + } else { + switch mode { + case .default: + self.titleView.title = presentationData.strings.Attachment_Gallery + case .wallpaper: + self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle + } + } } else { self.titleView.title = presentationData.strings.Attachment_Gallery } @@ -1316,7 +1334,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.navigationItem.titleView = self.titleView - if case let .assets(_, isStandalone) = self.subject, isStandalone { + if case let .assets(_, mode) = self.subject, mode != .default { self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) } else { if case let .assets(collection, _) = self.subject, collection != nil { @@ -1674,13 +1692,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } private func presentSearch(activateOnDisplay: Bool) { - guard self.moreButtonNode.iconNode.iconState == .search else { + guard self.moreButtonNode.iconNode.iconState == .search, case let .assets(_, mode) = self.subject else { return } self.requestAttachmentMenuExpansion() self.presentWebSearch(MediaGroupsScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mediaAssetsContext: self.controllerNode.mediaAssetsContext, openGroup: { [weak self] collection in if let strongSelf = self { - let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, threadTitle: strongSelf.threadTitle, chatLocation: strongSelf.chatLocation, bannedSendPhotos: strongSelf.bannedSendPhotos, bannedSendVideos: strongSelf.bannedSendVideos, subject: .assets(collection, false), editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState) + let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, threadTitle: strongSelf.threadTitle, chatLocation: strongSelf.chatLocation, bannedSendPhotos: strongSelf.bannedSendPhotos, bannedSendVideos: strongSelf.bannedSendVideos, subject: .assets(collection, mode), editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState) mediaPicker.presentSchedulePicker = strongSelf.presentSchedulePicker mediaPicker.presentTimerPicker = strongSelf.presentTimerPicker @@ -1968,3 +1986,15 @@ public class MediaPickerGridSelectionGesture : UIPanGestureRecognizer { self.updateIsScrollEnabled(true) } } + +public func standaloneMediaPickerController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: MediaPickerScreen.Subject, completion: @escaping (PHAsset) -> Void = { _ in }) -> ViewController { + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { + return nil + }) + controller.requestController = { _, present in + let mediaPickerController = MediaPickerScreen(context: context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: subject) + mediaPickerController.customSelection = completion + present(mediaPickerController, mediaPickerController.mediaPickerContext) + } + return controller +} diff --git a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift index 81dd23cf02e..7fe0210eebd 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift @@ -88,7 +88,7 @@ final class MediaPickerTitleView: UIView { let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: min(300.0, size.width - 36.0)), transition: .immediate) self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - controlSize.width) / 2.0), y: floorToScreenPixels((size.height - controlSize.height) / 2.0)), size: controlSize) - let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: 44.0)) + let titleSize = self.titleNode.updateLayout(CGSize(width: 210.0, height: 44.0)) self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) } } diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index 678a8fd26fe..d7c52ee05c7 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -112,6 +112,7 @@ swift_library( "//submodules/FeaturedStickersScreen:FeaturedStickersScreen", "//submodules/MediaPickerUI:MediaPickerUI", "//submodules/ImageBlur:ImageBlur", + "//submodules/AttachmentUI:AttachmentUI", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift b/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift index 74293fd2ba3..205059cf867 100644 --- a/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift @@ -309,6 +309,9 @@ class ReactionChatPreviewItemNode: ListViewItemNode { if let node = node { contentSize.height += node.frame.size.height } + if item.reaction == nil { + contentSize.height += 34.0 + } insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -333,7 +336,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode { strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize) - var topOffset: CGFloat = 16.0 + var topOffset: CGFloat = 16.0 + 17.0 if let node = node { strongSelf.messageNode = node if node.supernode == nil { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift index bc402517a9d..243de44ec9f 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift @@ -9,6 +9,7 @@ import LegacyComponents import TelegramPresentationData import TelegramUIPreferences import AccountContext +import AttachmentUI private func availableGradients(theme: PresentationTheme) -> [[UInt32]] { if theme.overallDarkAppearance { @@ -102,7 +103,7 @@ private func availableColors(theme: PresentationTheme) -> [UInt32] { } } -public final class ThemeColorsGridController: ViewController { +public final class ThemeColorsGridController: ViewController, AttachmentContainable { public enum Mode { case `default` case peer(EnginePeer) @@ -145,6 +146,10 @@ public final class ThemeColorsGridController: ViewController { private var previousContentOffset: GridNodeVisibleContentOffset? + fileprivate let mainButtonStatePromise = Promise(nil) + + var pushController: (ViewController) -> Void = { _ in } + public init(context: AccountContext, mode: Mode = .default) { self.context = context self.mode = mode @@ -152,7 +157,6 @@ public final class ThemeColorsGridController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) - self.title = self.presentationData.strings.WallpaperColors_Title self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.scrollToTop = { [weak self] in @@ -174,6 +178,18 @@ public final class ThemeColorsGridController: ViewController { } } }) + + if case .peer = mode { + self.title = self.presentationData.strings.Conversation_Theme_ChooseColorTitle + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Conversation_Theme_SetCustomColor, style: .plain, target: self, action: #selector(self.customPressed)) + } else { + self.title = self.presentationData.strings.WallpaperColors_Title + } + + self.pushController = { [weak self] controller in + self?.push(controller) + } } required public init(coder aDecoder: NSCoder) { @@ -184,6 +200,14 @@ public final class ThemeColorsGridController: ViewController { self.presentationDataDisposable?.dispose() } + @objc private func cancelPressed() { + self.dismiss() + } + + @objc private func customPressed() { + self.presentColorPicker() + } + private func updateThemeAndStrings() { self.title = self.presentationData.strings.WallpaperColors_Title self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -194,56 +218,68 @@ public final class ThemeColorsGridController: ViewController { } } + private func presentColorPicker() { + let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] sharedData in + guard let strongSelf = self else { + return + } + let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings + + let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered + let themeReference: PresentationThemeReference + if autoNightModeTriggered { + themeReference = settings.automaticThemeSwitchSetting.theme + } else { + themeReference = settings.theme + } + + let controller = ThemeAccentColorController(context: strongSelf.context, mode: .background(themeReference: themeReference), resultMode: strongSelf.mode.colorPickerMode) + controller.completion = { [weak self] in + if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { + var controllers = navigationController.viewControllers + controllers = controllers.filter { controller in + if controller is ThemeColorsGridController { + return false + } + return true + } + navigationController.setViewControllers(controllers, animated: false) + controllers = controllers.filter { controller in + if controller is ThemeAccentColorController { + return false + } + return true + } + navigationController.setViewControllers(controllers, animated: true) + } + } + strongSelf.pushController(controller) + }) + } + public override func loadDisplayNode() { self.displayNode = ThemeColorsGridControllerNode(context: self.context, presentationData: self.presentationData, controller: self, gradients: availableGradients(theme: self.presentationData.theme), colors: availableColors(theme: self.presentationData.theme), push: { [weak self] controller in - self?.push(controller) + self?.pushController(controller) }, pop: { [weak self] in if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { let _ = navigationController.popViewController(animated: true) } }, presentColorPicker: { [weak self] in if let strongSelf = self { - let _ = (strongSelf.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] sharedData in - guard let strongSelf = self else { - return - } - let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings - - let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered - let themeReference: PresentationThemeReference - if autoNightModeTriggered { - themeReference = settings.automaticThemeSwitchSetting.theme - } else { - themeReference = settings.theme - } - - let controller = ThemeAccentColorController(context: strongSelf.context, mode: .background(themeReference: themeReference), resultMode: strongSelf.mode.colorPickerMode) - controller.completion = { [weak self] in - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - controllers = controllers.filter { controller in - if controller is ThemeColorsGridController { - return false - } - return true - } - navigationController.setViewControllers(controllers, animated: false) - controllers = controllers.filter { controller in - if controller is ThemeAccentColorController { - return false - } - return true - } - navigationController.setViewControllers(controllers, animated: true) - } - } - strongSelf.push(controller) - }) + strongSelf.presentColorPicker() } }) + let transitionOffset: CGFloat + switch self.mode { + case .default: + transitionOffset = 30.0 + case .peer: + transitionOffset = 2.0 + } + self.controllerNode.gridNode.visibleContentOffsetChanged = { [weak self] offset in if let strongSelf = self { var previousContentOffsetValue: CGFloat? @@ -253,12 +289,12 @@ public final class ThemeColorsGridController: ViewController { switch offset { case let .known(value): let transition: ContainedViewLayoutTransition - if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 30.0 { + if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > transitionOffset { transition = .animated(duration: 0.2, curve: .easeInOut) } else { transition = .immediate } - strongSelf.navigationBar?.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition) + strongSelf.navigationBar?.updateBackgroundAlpha(min(transitionOffset, value) / transitionOffset, transition: transition) case .unknown, .none: strongSelf.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate) } @@ -279,4 +315,71 @@ public final class ThemeColorsGridController: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } + + @objc fileprivate func mainButtonPressed() { + + } + + public var requestAttachmentMenuExpansion: () -> Void = {} + public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var cancelPanGesture: () -> Void = { } + public var isContainerPanning: () -> Bool = { return false } + public var isContainerExpanded: () -> Bool = { return false } + + public var mediaPickerContext: AttachmentMediaPickerContext? { + return ThemeColorsGridContext(controller: self) + } +} + +private final class ThemeColorsGridContext: AttachmentMediaPickerContext { + private weak var controller: ThemeColorsGridController? + + var selectionCount: Signal { + return .single(0) + } + + var caption: Signal { + return .single(nil) + } + + public var loadingProgress: Signal { + return .single(nil) + } + + public var mainButtonState: Signal { + return self.controller?.mainButtonStatePromise.get() ?? .single(nil) + } + + init(controller: ThemeColorsGridController) { + self.controller = controller + } + + func setCaption(_ caption: NSAttributedString) { + } + + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + } + + func schedule() { + } + + func mainButtonAction() { + self.controller?.mainButtonPressed() + } +} + + +public func standaloneColorPickerController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, push: @escaping (ViewController) -> Void) -> ViewController { + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { + return nil + }) + controller.requestController = { _, present in + let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer)) + colorPickerController.pushController = { controller in + push(controller) + } + present(colorPickerController, colorPickerController.mediaPickerContext) + } + return controller } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift index 72d0a6ca957..94bbdcb6d1c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift @@ -73,7 +73,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { let ready = ValuePromise() - private var backgroundNode: ASDisplayNode + private var topBackgroundNode: ASDisplayNode private var separatorNode: ASDisplayNode private let customColorItemNode: ItemListActionItemNode @@ -102,8 +102,8 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { self.rightOverlayNode = ASDisplayNode() self.rightOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor - self.backgroundNode = ASDisplayNode() - self.backgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor + self.topBackgroundNode = ASDisplayNode() + self.topBackgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor self.separatorNode = ASDisplayNode() self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor @@ -121,9 +121,14 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor - self.gridNode.addSubnode(self.backgroundNode) - self.gridNode.addSubnode(self.separatorNode) - self.gridNode.addSubnode(self.customColorItemNode) + if case .default = controller.mode { + self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor + self.gridNode.addSubnode(self.topBackgroundNode) + self.gridNode.addSubnode(self.separatorNode) + self.gridNode.addSubnode(self.customColorItemNode) + } else { + self.backgroundColor = presentationData.theme.list.plainBackgroundColor + } self.addSubnode(self.gridNode) let previousEntries = Atomic<[ThemeColorsGridControllerEntry]?>(value: nil) @@ -240,9 +245,13 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData - self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor - self.leftOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor - self.rightOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor + if let controller = self.controller, case .default = controller.mode { + self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor + self.leftOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor + self.rightOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor + } else { + self.backgroundColor = presentationData.theme.list.plainBackgroundColor + } self.customColorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.WallpaperColors_SetCustomColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in self?.presentColorPicker() @@ -272,6 +281,9 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + guard let controller = self.controller else { + return + } let hadValidLayout = self.validLayout != nil var insets = layout.insets(options: [.input]) @@ -280,7 +292,18 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { insets.right = layout.safeInsets.right let scrollIndicatorInsets = insets - let minSpacing: CGFloat = 8.0 + let itemsPerRow: Int + if case .compact = layout.metrics.widthClass { + switch layout.orientation { + case .portrait: + itemsPerRow = 3 + case .landscape: + itemsPerRow = 5 + } + } else { + itemsPerRow = 3 + } + let referenceImageSize: CGSize let screenWidth = min(layout.size.width, layout.size.height) if screenWidth >= 375.0 { @@ -288,29 +311,56 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { } else { referenceImageSize = CGSize(width: 91.0, height: 91.0) } - let imageCount = Int((layout.size.width - insets.left - insets.right - minSpacing * 2.0) / (referenceImageSize.width + minSpacing)) - let imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((layout.size.width - CGFloat(imageCount + 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height)) - let spacing = floor((layout.size.width - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount + 1)) + + let width = layout.size.width - layout.safeInsets.left - layout.safeInsets.right + let imageSize: CGSize + let spacing: CGFloat + var fillWidth: Bool? + if case .peer = controller.mode { + spacing = 1.0 + + let itemWidth = floorToScreenPixels((width - spacing * CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow)) + imageSize = CGSize(width: itemWidth, height: itemWidth) + fillWidth = true + } else { + let minSpacing = 8.0 + + imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((width - CGFloat(itemsPerRow + 1) * minSpacing) / CGFloat(itemsPerRow)), height: referenceImageSize.height)) + spacing = floor((width - CGFloat(itemsPerRow) * imageSize.width) / CGFloat(itemsPerRow + 1)) + } + + let buttonTopInset: CGFloat = 32.0 + let buttonHeight: CGFloat = 44.0 + let buttonBottomInset: CGFloat = 35.0 + + var buttonInset: CGFloat = buttonTopInset + buttonHeight + buttonBottomInset + var buttonOffset = buttonInset + 10.0 var listInsets = insets - if layout.size.width >= 375.0 { - let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) - listInsets.left += inset - listInsets.right += inset - - if self.leftOverlayNode.supernode == nil { - self.gridNode.addSubnode(self.leftOverlayNode) - } - if self.rightOverlayNode.supernode == nil { - self.gridNode.addSubnode(self.rightOverlayNode) + if case .default = controller.mode { + if layout.size.width >= 375.0 { + let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) + listInsets.left += inset + listInsets.right += inset + + if self.leftOverlayNode.supernode == nil { + self.gridNode.addSubnode(self.leftOverlayNode) + } + if self.rightOverlayNode.supernode == nil { + self.gridNode.addSubnode(self.rightOverlayNode) + } + } else { + if self.leftOverlayNode.supernode != nil { + self.leftOverlayNode.removeFromSupernode() + } + if self.rightOverlayNode.supernode != nil { + self.rightOverlayNode.removeFromSupernode() + } } } else { - if self.leftOverlayNode.supernode != nil { - self.leftOverlayNode.removeFromSupernode() - } - if self.rightOverlayNode.supernode != nil { - self.rightOverlayNode.removeFromSupernode() - } + self.customColorItemNode.isHidden = true + buttonOffset = 0.0 + buttonInset = 0.0 } let makeColorLayout = self.customColorItemNode.asyncLayout() @@ -318,14 +368,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { let (colorLayout, colorApply) = makeColorLayout(self.customColorItem, params, ItemListNeighbors(top: .none, bottom: .none)) colorApply() - let buttonTopInset: CGFloat = 32.0 - let buttonHeight: CGFloat = 44.0 - let buttonBottomInset: CGFloat = 35.0 - - let buttonInset: CGFloat = buttonTopInset + buttonHeight + buttonBottomInset - let buttonOffset = buttonInset + 10.0 - - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 500.0))) + transition.updateFrame(node: self.topBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 500.0))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonInset - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) transition.updateFrame(node: self.customColorItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonTopInset), size: colorLayout.contentSize)) @@ -334,7 +377,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { insets.top += spacing + buttonInset - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: nil, lineSpacing: spacing, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: fillWidth, lineSpacing: spacing, itemSpacing: fillWidth != nil ? spacing : nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) @@ -350,7 +393,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.backgroundNode.layer.position, duration: duration) + self.topBackgroundNode.layer.animatePosition(from: self.topBackgroundNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.topBackgroundNode.layer.position, duration: duration) self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.separatorNode.layer.position, duration: duration) self.customColorItemNode.layer.animatePosition(from: self.customColorItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.customColorItemNode.layer.position, duration: duration) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index 852059bd5f4..14658adb61b 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -142,7 +142,7 @@ public final class ThemeGridController: ViewController { } }, presentGallery: { [weak self] in if let strongSelf = self { - let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, true)) + let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper)) controller.customSelection = { [weak self] asset in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c4ea3593348..68f9c899aff 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -13826,7 +13826,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(actionSheet, in: .window(.root)) } - private func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, false), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { + private func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } @@ -18511,7 +18511,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let dismissControllers = { [weak self] in if let self, let navigationController = self.navigationController as? NavigationController { let controllers = navigationController.viewControllers.filter({ controller in - if controller is WallpaperGalleryController || controller is MediaPickerScreen { + if controller is WallpaperGalleryController || controller is AttachmentController { return false } return true @@ -18520,9 +18520,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, true)) - controller.navigationPresentation = .modal - controller.customSelection = { [weak self] asset in + let controller = standaloneMediaPickerController(context: strongSelf.context, subject: .assets(nil, .wallpaper), completion: { asset in guard let strongSelf = self else { return } @@ -18536,7 +18534,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } strongSelf.push(controller) - } + }) + controller.navigationPresentation = .flatModal strongSelf.push(controller) }, changeColor: { @@ -18547,8 +18546,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.themeScreen = nil themeController.dimTapped() } - let controller = ThemeColorsGridController(context: context, mode: .peer(EnginePeer(peer))) - controller.navigationPresentation = .modal + let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), push: { [weak self] controller in + if let strongSelf = self { + strongSelf.push(controller) + } + }) + controller.navigationPresentation = .flatModal strongSelf.push(controller) }, completion: { [weak self] emoticon in From 34062b0a06a4172597284b423349b806b53120f2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 6 Apr 2023 17:53:37 +0400 Subject: [PATCH 06/57] Chat wallpaper removal --- .../Sources/AttachmentPanel.swift | 33 +++++++-- .../Sources/MediaPickerScreen.swift | 69 +++++++++++++------ .../PremiumUI/Sources/PremiumGiftScreen.swift | 2 +- .../Themes/ThemeColorsGridController.swift | 15 +++- .../TelegramUI/Sources/ChatController.swift | 43 ++++++++---- .../WebUI/Sources/WebAppController.swift | 2 +- 6 files changed, 119 insertions(+), 45 deletions(-) diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index c6062f9f638..18b04c58033 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -387,7 +387,13 @@ public struct AttachmentMainButtonState { case center } + public enum Font: Equatable { + case regular + case bold + } + public let text: String? + public let font: Font public let background: Background public let textColor: UIColor public let isVisible: Bool @@ -396,6 +402,7 @@ public struct AttachmentMainButtonState { public init( text: String?, + font: Font, background: Background, textColor: UIColor, isVisible: Bool, @@ -403,6 +410,7 @@ public struct AttachmentMainButtonState { isEnabled: Bool ) { self.text = text + self.font = font self.background = background self.textColor = textColor self.isVisible = isVisible @@ -411,7 +419,7 @@ public struct AttachmentMainButtonState { } static var initial: AttachmentMainButtonState { - return AttachmentMainButtonState(text: nil, background: .color(.clear), textColor: .clear, isVisible: false, progress: .none, isEnabled: false) + return AttachmentMainButtonState(text: nil, font: .bold, background: .color(.clear), textColor: .clear, isVisible: false, progress: .none, isEnabled: false) } } @@ -643,7 +651,14 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.setupShimmering() if let text = state.text { - self.textNode.attributedText = NSAttributedString(string: text, font: Font.semibold(17.0), textColor: state.textColor) + let font: UIFont + switch state.font { + case .regular: + font = Font.regular(17.0) + case .bold: + font = Font.semibold(17.0) + } + self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: state.textColor) let textSize = self.textNode.updateLayout(size) self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) @@ -1267,7 +1282,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { func updateMainButtonState(_ mainButtonState: AttachmentMainButtonState?) { var currentButtonState = self.mainButtonState if mainButtonState == nil { - currentButtonState = AttachmentMainButtonState(text: currentButtonState.text, background: currentButtonState.background, textColor: currentButtonState.textColor, isVisible: false, progress: .none, isEnabled: currentButtonState.isEnabled) + currentButtonState = AttachmentMainButtonState(text: currentButtonState.text, font: currentButtonState.font, background: currentButtonState.background, textColor: currentButtonState.textColor, isVisible: false, progress: .none, isEnabled: currentButtonState.isEnabled) } self.mainButtonState = mainButtonState ?? currentButtonState } @@ -1417,6 +1432,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.scrollNode.isUserInteractionEnabled = !isSelecting let isButtonVisible = self.mainButtonState.isVisible + let isNarrowButton = isButtonVisible && self.mainButtonState.font == .regular var insets = layout.insets(options: []) if let inputHeight = layout.inputHeight, inputHeight > 0.0 && (isSelecting || isButtonVisible) { @@ -1457,7 +1473,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { if isButtonVisible { var height: CGFloat if layout.intrinsicInsets.bottom > 0.0 && (layout.inputHeight ?? 0.0).isZero { - height = bounds.height + 9.0 + height = bounds.height if case .regular = layout.metrics.widthClass { if self.isStandalone { height -= 3.0 @@ -1466,7 +1482,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } } } else { - height = bounds.height + 9.0 + 8.0 + height = bounds.height + 8.0 + } + if !isNarrowButton { + height += 9.0 } containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: height)) } else if isSelecting { @@ -1532,11 +1551,13 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { let sideInset: CGFloat = 16.0 let buttonSize = CGSize(width: layout.size.width - (sideInset + layout.safeInsets.left) * 2.0, height: 50.0) + let buttonTopInset: CGFloat = isNarrowButton ? 2.0 : 8.0 + if !self.dismissed { self.mainButtonNode.updateLayout(size: buttonSize, state: self.mainButtonState, transition: transition) } if !self.animatingTransition { - transition.updateFrame(node: self.mainButtonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + sideInset, y: isButtonVisible || self.fromMenu ? 8.0 : containerFrame.height), size: buttonSize)) + transition.updateFrame(node: self.mainButtonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + sideInset, y: isButtonVisible || self.fromMenu ? buttonTopInset : containerFrame.height), size: buttonSize)) } return containerFrame.height diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 7da26bf2711..c06f8529604 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1238,7 +1238,24 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private var isDismissing = false - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, threadTitle: String?, chatLocation: ChatLocation?, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, subject: Subject, editingContext: TGMediaEditingContext? = nil, selectionContext: TGMediaSelectionContext? = nil, saveEditedPhotos: Bool = false) { + fileprivate let mainButtonState: AttachmentMainButtonState? + private let mainButtonAction: (() -> Void)? + + public init( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peer: EnginePeer?, + threadTitle: String?, + chatLocation: ChatLocation?, + bannedSendPhotos: (Int32, Bool)?, + bannedSendVideos: (Int32, Bool)?, + subject: Subject, + editingContext: TGMediaEditingContext? = nil, + selectionContext: TGMediaSelectionContext? = nil, + saveEditedPhotos: Bool = false, + mainButtonState: AttachmentMainButtonState? = nil, + mainButtonAction: (() -> Void)? = nil + ) { self.context = context let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } @@ -1251,6 +1268,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.bannedSendVideos = bannedSendVideos self.subject = subject self.saveEditedPhotos = saveEditedPhotos + self.mainButtonState = mainButtonState + self.mainButtonAction = mainButtonAction let selectionContext = selectionContext ?? TGMediaSelectionContext() @@ -1627,6 +1646,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } } + func mainButtonPressed() { + self.mainButtonAction?() + } + func dismissAllTooltips() { self.undoOverlayController?.dismissWithCommitAction() } @@ -1811,21 +1834,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } public var mediaPickerContext: AttachmentMediaPickerContext? { - if let interaction = self.interaction { - return MediaPickerContext(interaction: interaction) - } else { - return nil - } + return MediaPickerContext(controller: self) } } final class MediaPickerContext: AttachmentMediaPickerContext { - private weak var interaction: MediaPickerInteraction? + private weak var controller: MediaPickerScreen? var selectionCount: Signal { return Signal { [weak self] subscriber in - let disposable = self?.interaction?.selectionState?.selectionChangedSignal().start(next: { [weak self] value in - subscriber.putNext(Int(self?.interaction?.selectionState?.count() ?? 0)) + let disposable = self?.controller?.interaction?.selectionState?.selectionChangedSignal().start(next: { [weak self] value in + subscriber.putNext(Int(self?.controller?.interaction?.selectionState?.count() ?? 0)) }, error: { _ in }, completed: { }) return ActionDisposable { disposable?.dispose() @@ -1835,7 +1854,7 @@ final class MediaPickerContext: AttachmentMediaPickerContext { var caption: Signal { return Signal { [weak self] subscriber in - let disposable = self?.interaction?.editingState.forcedCaption().start(next: { caption in + let disposable = self?.controller?.interaction?.editingState.forcedCaption().start(next: { caption in if let caption = caption as? NSAttributedString { subscriber.putNext(caption) } else { @@ -1853,27 +1872,27 @@ final class MediaPickerContext: AttachmentMediaPickerContext { } public var mainButtonState: Signal { - return .single(nil) + return .single(self.controller?.mainButtonState) } - init(interaction: MediaPickerInteraction) { - self.interaction = interaction + init(controller: MediaPickerScreen) { + self.controller = controller } func setCaption(_ caption: NSAttributedString) { - self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true) + self.controller?.interaction?.editingState.setForcedCaption(caption, skipUpdate: true) } func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { - self.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, {}) + self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, {}) } func schedule() { - self.interaction?.schedule() + self.controller?.interaction?.schedule() } func mainButtonAction() { - + self.controller?.mainButtonPressed() } } @@ -1987,12 +2006,22 @@ public class MediaPickerGridSelectionGesture : UIPanGestureRecognizer { } } -public func standaloneMediaPickerController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: MediaPickerScreen.Subject, completion: @escaping (PHAsset) -> Void = { _ in }) -> ViewController { +public func wallpaperMediaPickerController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peer: EnginePeer, + canDelete: Bool, + completion: @escaping (PHAsset) -> Void = { _ in } +) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil }) - controller.requestController = { _, present in - let mediaPickerController = MediaPickerScreen(context: context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: subject) + controller.requestController = { [weak controller] _, present in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let mediaPickerController = MediaPickerScreen(context: context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper), mainButtonState: canDelete ? AttachmentMainButtonState(text: presentationData.strings.Conversation_Theme_ResetWallpaper, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true) : nil, mainButtonAction: canDelete ? { + let _ = context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: nil).start() + controller?.dismiss(animated: true) + } : nil) mediaPickerController.customSelection = completion present(mediaPickerController, mediaPickerController.mediaPickerContext) } diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index adf80c4f646..bfc61df3657 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -645,7 +645,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent { price = nil } let buttonText = presentationData.strings.Premium_Gift_GiftSubscription(price ?? "—").string - self.buttonStatePromise.set(.single(AttachmentMainButtonState(text: buttonText, background: .premium, textColor: .white, isVisible: true, progress: self.inProgress ? .center : .none, isEnabled: true))) + self.buttonStatePromise.set(.single(AttachmentMainButtonState(text: buttonText, font: .bold, background: .premium, textColor: .white, isVisible: true, progress: self.inProgress ? .center : .none, isEnabled: true))) } func buy() { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift index 243de44ec9f..a4b30a3610a 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift @@ -150,7 +150,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina var pushController: (ViewController) -> Void = { _ in } - public init(context: AccountContext, mode: Mode = .default) { + public init(context: AccountContext, mode: Mode = .default, canDelete: Bool = false) { self.context = context self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -190,6 +190,10 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina self.pushController = { [weak self] controller in self?.push(controller) } + + if canDelete { + self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: self.presentationData.strings.Conversation_Theme_ResetWallpaper, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true))) + } } required public init(coder aDecoder: NSCoder) { @@ -317,7 +321,12 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina } @objc fileprivate func mainButtonPressed() { + guard case let .peer(peer) = self.mode else { + return + } + let _ = self.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: nil).start() + self.dismiss(animated: true) } public var requestAttachmentMenuExpansion: () -> Void = {} @@ -370,12 +379,12 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext { } -public func standaloneColorPickerController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, push: @escaping (ViewController) -> Void) -> ViewController { +public func standaloneColorPickerController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, canDelete: Bool, push: @escaping (ViewController) -> Void) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil }) controller.requestController = { _, present in - let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer)) + let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer), canDelete: canDelete) colorPickerController.pushController = { controller in push(controller) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 68f9c899aff..bbd85374e45 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -18520,21 +18520,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - let controller = standaloneMediaPickerController(context: strongSelf.context, subject: .assets(nil, .wallpaper), completion: { asset in - guard let strongSelf = self else { - return - } - let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) - controller.navigationPresentation = .modal - controller.apply = { [weak self] wallpaper, options, cropRect in - if let strongSelf = self { - uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, peerId: peerId, completion: { - dismissControllers() - }) + var canDelete = false + if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { + canDelete = cachedUserData.wallpaper != nil + } + let controller = wallpaperMediaPickerController( + context: strongSelf.context, + updatedPresentationData: strongSelf.updatedPresentationData, + peer: EnginePeer(peer), + canDelete: canDelete, + completion: { asset in + guard let strongSelf = self else { + return + } + let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) + controller.navigationPresentation = .modal + controller.apply = { [weak self] wallpaper, options, cropRect in + if let strongSelf = self { + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, peerId: peerId, completion: { + dismissControllers() + }) + } } + strongSelf.push(controller) } - strongSelf.push(controller) - }) + ) controller.navigationPresentation = .flatModal strongSelf.push(controller) }, @@ -18546,7 +18556,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.themeScreen = nil themeController.dimTapped() } - let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), push: { [weak self] controller in + + var canDelete = false + if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { + canDelete = cachedUserData.wallpaper != nil + } + let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), canDelete: canDelete, push: { [weak self] controller in if let strongSelf = self { strongSelf.push(controller) } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index af5b0191e1c..e1b7eb78164 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -675,7 +675,7 @@ public final class WebAppController: ViewController, AttachmentContainable { let isLoading = json["is_progress_visible"] as? Bool let isEnabled = json["is_active"] as? Bool - let state = AttachmentMainButtonState(text: text, background: .color(backgroundColor), textColor: textColor, isVisible: isVisible, progress: (isLoading ?? false) ? .side : .none, isEnabled: isEnabled ?? true) + let state = AttachmentMainButtonState(text: text, font: .bold, background: .color(backgroundColor), textColor: textColor, isVisible: isVisible, progress: (isLoading ?? false) ? .side : .none, isEnabled: isEnabled ?? true) self.mainButtonState = state } } From bf13f1752184697212a3cf41c1b8078945de1935 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 6 Apr 2023 19:19:47 +0400 Subject: [PATCH 07/57] Folder improvements --- .../ChatListFilterPresetController.swift | 91 +++++++++++++------ .../Sources/PremiumLimitScreen.swift | 11 ++- .../Animations/anim_remove_from_folder.json | 1 + 3 files changed, 71 insertions(+), 32 deletions(-) create mode 100644 submodules/TelegramUI/Resources/Animations/anim_remove_from_folder.json diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index db327fd667a..3d4d21fdbb3 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -840,37 +840,40 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f } includePeers.sort() - let newPeers = includePeers.filter({ !(filter.data?.includePeers.peers.contains($0) ?? false) }) - var removedPeers: [PeerId] = [] - if let data = filter.data { - removedPeers = data.includePeers.peers.filter({ !includePeers.contains($0) }) - } - if newPeers.count != 0 { - let title: String - let text: String - - if newPeers.count == 1 { - title = "Сhat added to folder" - text = "It will not affect chatlist of the links of this folder" - } else { - title = "\(newPeers.count) chats added to folder" - text = "It will not affect chatlist of the links of this folder" + if filter.id > 1, case let .filter(_, _, _, data) = filter, data.hasSharedLinks { + let newPeers = includePeers.filter({ !(filter.data?.includePeers.peers.contains($0) ?? false) }) + var removedPeers: [PeerId] = [] + if let data = filter.data { + removedPeers = data.includePeers.peers.filter({ !includePeers.contains($0) }) } - - presentUndo(.info(title: title, text: text, timeout: nil)) - } else if removedPeers.count != 0 { - let title: String - let text: String - - if newPeers.count == 1 { - title = "Сhat removed from folder" - text = "It will not affect chatlist of the links of this folder" - } else { - title = "\(newPeers.count) chats removed from folder" - text = "It will not affect chatlist of the links of this folder" + if newPeers.count != 0 { + let title: String + let text: String + + //TODO:localize + if newPeers.count == 1 { + title = "Сhat added to folder" + text = "It will not affect chatlist of the links of this folder" + } else { + title = "\(newPeers.count) chats added to folder" + text = "It will not affect chatlist of the links of this folder" + } + + presentUndo(.universal(animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil)) + } else if removedPeers.count != 0 { + let title: String + let text: String + + if newPeers.count == 1 { + title = "Сhat removed from folder" + text = "It will not affect chatlist of the links of this folder" + } else { + title = "\(newPeers.count) chats removed from folder" + text = "It will not affect chatlist of the links of this folder" + } + + presentUndo(.universal(animation: "anim_remove_from_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil)) } - - presentUndo(.info(title: title, text: text, timeout: nil)) } var categories: ChatListFilterPeerCategories = [] @@ -1321,6 +1324,21 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi } return state } + + let _ = (updatedCurrentPreset |> take(1) |> deliverOnMainQueue).start(next: { currentPreset in + if let currentPreset, let data = currentPreset.data, data.hasSharedLinks { + //TODO:localize + let title: String + let text: String + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + title = "Сhat removed from folder" + text = "It will not affect chatlist of the links of this folder" + + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_remove_from_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + } + }) }, deleteExcludePeer: { peerId in updateState { state in @@ -1581,6 +1599,21 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi } return state } + + let _ = (updatedCurrentPreset |> take(1) |> deliverOnMainQueue).start(next: { currentPreset in + if let currentPreset, let data = currentPreset.data, data.hasSharedLinks { + //TODO:localize + let title: String + let text: String + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + title = "Сhat removed from folder" + text = "It will not affect chatlist of the links of this folder" + + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_remove_from_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + } + }) }))) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index 7dd8b4ac1d8..25f2ab09a1c 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -606,14 +606,19 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { let activityPosition = floor(context.availableSize.width * component.badgeGraphPosition) + var inactiveTitleOpacity: CGFloat = 1.0 var inactiveValueOpacity: CGFloat = 1.0 - if inactiveValue.size.width + inactiveTitle.size.width >= activityPosition - 8.0 { - inactiveValueOpacity = 0.0 + + if 12.0 + inactiveValue.size.width + 4.0 + inactiveTitle.size.width + 12.0 >= activityPosition - 8.0 { + inactiveTitleOpacity = 0.0 + if 12.0 + inactiveValue.size.width + 12.0 >= activityPosition - 8.0 { + inactiveValueOpacity = 0.0 + } } context.add(inactiveTitle .position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0)) - .opacity(inactiveValueOpacity) + .opacity(inactiveTitleOpacity) ) context.add(inactiveValue diff --git a/submodules/TelegramUI/Resources/Animations/anim_remove_from_folder.json b/submodules/TelegramUI/Resources/Animations/anim_remove_from_folder.json new file mode 100644 index 00000000000..5ca19d8603c --- /dev/null +++ b/submodules/TelegramUI/Resources/Animations/anim_remove_from_folder.json @@ -0,0 +1 @@ +{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":54,"w":400,"h":350,"nm":"Folder Out New 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Arrow 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.655],"y":[-0.884]},"o":{"x":[0.326],"y":[5.74]},"t":27,"s":[-10.082]},{"i":{"x":[0.648],"y":[1.018]},"o":{"x":[0.318],"y":[0.111]},"t":30,"s":[-11.275]},{"i":{"x":[0.613],"y":[1]},"o":{"x":[0.289],"y":[0.151]},"t":33,"s":[-30]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":41,"s":[-25]},{"t":50,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.649,"y":1},"o":{"x":0.553,"y":0.281},"t":27,"s":[153.06,67.855,0],"to":[-103.81,-23.605,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":45,"s":[74.753,206.779,0],"to":[0,0,0],"ti":[0,0,0]},{"t":53,"s":[74.753,178.779,0]}],"ix":2},"a":{"a":0,"k":[-125.497,4.029,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":27,"s":[{"i":[[0,0],[11.608,-1.924],[8.098,-2.588],[0,0],[-1.468,0],[0,0],[0,0],[-0.83,-0.012],[-1.731,0.026],[-4.322,4.661],[0,0],[0,0],[3.535,0.518],[0,0],[1.174,0],[0,-0.025]],"o":[[0,0],[-5.659,0.938],[-53.918,17.233],[0,1.231],[0,0],[0,0],[0,0.017],[1.73,0.026],[0,0],[4.322,-4.661],[0,0],[1.196,-1.448],[-20.978,-3.073],[-0.831,-0.008],[-2.447,0],[0,0]],"v":[[-144.218,-10.647],[-167.073,-8.009],[-188.918,-3.005],[-234.982,19.257],[-232.323,21.486],[-142.067,19.122],[-141.199,19.014],[-139.902,19.061],[-112.739,17.694],[-101.876,4.123],[-85.409,-3.347],[-85.876,-3.687],[-86.394,-8.424],[-133.697,-11.082],[-137.701,-11.004],[-142.972,-10.683]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":33,"s":[{"i":[[0,0],[0,0],[0,-1.12],[0,0],[-1.657,0],[0,0],[0,0],[-0.937,-0.634],[-1.953,1.319],[0,0],[0,0],[0,0],[1.739,1.171],[0,0],[1.325,0],[0,-1.866]],"o":[[0,0],[-1.657,0],[0,0],[0,1.12],[0,0],[0,0],[0,0.896],[1.952,1.32],[0,0],[0,0],[0,0],[1.349,-1.317],[0,0],[-0.938,-0.633],[-2.761,0],[0,0]],"v":[[-136.662,-11.028],[-154.169,-11.028],[-157.169,-9],[-157.168,13.734],[-154.168,15.761],[-136.661,15.762],[-136.661,34.474],[-135.198,36.863],[-128.126,36.864],[-105.302,21.449],[-80.17,4.475],[-79.586,4.007],[-80.17,-0.302],[-128.129,-32.684],[-131.662,-33.673],[-136.662,-30.294]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.3,"y":0},"t":45,"s":[{"i":[[0,0],[0,0],[0,-1.441],[0,0],[-1.657,0],[0,0],[0,0],[-0.937,-0.816],[-1.953,1.698],[0,0],[0,0],[0,0],[1.739,1.507],[0,0],[1.325,0],[0,-2.402]],"o":[[0,0],[-1.657,0],[0,0],[0,1.441],[0,0],[0,0],[0,1.153],[1.952,1.699],[0,0],[0,0],[0,0],[1.349,-1.696],[0,0],[-0.938,-0.815],[-2.761,0],[0,0]],"v":[[-144.223,-12.822],[-161.73,-12.822],[-164.73,-10.212],[-164.73,19.046],[-161.73,21.655],[-144.223,21.655],[-144.223,45.738],[-142.76,48.812],[-135.689,48.814],[-112.864,28.974],[-87.731,7.128],[-87.147,6.526],[-87.732,0.98],[-135.689,-40.694],[-139.223,-41.966],[-144.223,-37.617]],"c":true}]},{"t":53,"s":[{"i":[[0,0],[0,0],[0,-1.657],[0,0],[-1.657,0],[0,0],[0,0],[-0.937,-0.938],[-1.953,1.952],[0,0],[0,0],[0,0],[1.739,1.732],[0,0],[1.325,0],[0,-2.761]],"o":[[0,0],[-1.657,0],[0,0],[0,1.657],[0,0],[0,0],[0,1.326],[1.952,1.953],[0,0],[0,0],[0,0],[1.349,-1.949],[0,0],[-0.938,-0.937],[-2.761,0],[0,0]],"v":[[-144.223,-15.381],[-161.73,-15.381],[-164.73,-12.381],[-164.73,21.257],[-161.73,24.257],[-144.223,24.257],[-144.223,51.945],[-142.76,55.479],[-135.689,55.481],[-112.864,32.671],[-87.731,7.556],[-87.147,6.863],[-87.732,0.487],[-135.689,-47.424],[-139.223,-48.887],[-144.223,-43.887]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-0.004,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path-5","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":27,"op":115,"st":-5,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Folder Front","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.78,31.05,0],"ix":2},"a":{"a":0,"k":[57.78,31.05,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":3,"s":[{"i":[[0,0],[2.227,-6.498],[0,0],[0,-1.195],[-11.046,0],[0,0],[-2.227,6.498],[0,0],[0,1.195],[11.046,0]],"o":[[-9.176,0],[0,0],[-0.397,1.159],[0,8.063],[0,0],[9.176,0],[0,0],[0.397,-1.159],[0,-8.063],[0,0]],"v":[[-12.096,6.216],[-31.498,17.271],[-48.512,66.908],[-49.11,70.451],[-29.11,85.05],[127.657,85.05],[147.059,73.994],[163.661,26.719],[164.259,23.176],[144.259,8.577]],"c":true}]},{"i":{"x":0.5,"y":1},"o":{"x":0.4,"y":0},"t":26,"s":[{"i":[[0,0],[5.516,-7.616],[0,0],[0,-1.636],[-11.046,0],[0,0],[-3.702,4.718],[0,0],[-0.287,1.611],[17.899,0.533]],"o":[[-13.886,0.099],[0,0],[-0.397,1.588],[0,11.046],[0,0],[9.176,0],[0,0],[1.817,-2.298],[2.019,-11.354],[0,0]],"v":[[15.169,-0.792],[-12.083,14.287],[-46.038,57.422],[-49.11,65.05],[-29.11,85.05],[127.657,85.05],[147.059,69.904],[179.824,27.398],[186.808,16.342],[164.086,-0.858]],"c":true}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0},"t":42,"s":[{"i":[[0,0],[2.227,-8.902],[0,0],[0,-1.636],[-11.046,0],[0,0],[-2.227,8.902],[0,0],[0,1.636],[11.046,0]],"o":[[-9.176,0],[0,0],[-0.397,1.588],[0,11.046],[0,0],[9.176,0],[0,0],[0.397,-1.588],[0,-11.046],[0,0]],"v":[[-19.91,-30.122],[-39.312,-14.977],[-48.512,60.196],[-49.11,65.05],[-29.11,85.05],[127.657,85.05],[147.059,69.904],[156.259,-5.268],[156.857,-10.122],[136.857,-30.122]],"c":true}]},{"t":52,"s":[{"i":[[0,0],[2.227,-8.902],[0,0],[0,-1.636],[-11.046,0],[0,0],[-2.227,8.902],[0,0],[0,1.636],[11.046,0]],"o":[[-9.176,0],[0,0],[-0.397,1.588],[0,11.046],[0,0],[9.176,0],[0,0],[0.397,-1.588],[0,-11.046],[0,0]],"v":[[-12.096,-22.95],[-31.498,-7.804],[-48.512,60.196],[-49.11,65.05],[-29.11,85.05],[127.657,85.05],[147.059,69.904],[164.072,1.904],[164.671,-2.95],[144.671,-22.95]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Paper","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.562],"y":[0.795]},"o":{"x":[0.193],"y":[0]},"t":8,"s":[-2]},{"i":{"x":[0.655],"y":[4.184]},"o":{"x":[0.314],"y":[-0.57]},"t":15,"s":[-4]},{"i":{"x":[0.718],"y":[0.734]},"o":{"x":[0.356],"y":[0.331]},"t":20.154,"s":[-3.621]},{"t":28,"s":[-9.363]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.322,"y":0},"t":8,"s":[257.567,220.153,0],"to":[0.382,-4.007,0],"ti":[2.941,3.83,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11.785,"s":[262.921,208.399,0],"to":[-5.646,-7.352,0],"ti":[0.534,6.723,0]},{"i":{"x":0.661,"y":0.618},"o":{"x":0.167,"y":0.167},"t":15,"s":[237.946,187.294,0],"to":[-1.543,-19.419,0],"ti":[6.059,14.623,0]},{"i":{"x":0.67,"y":0.693},"o":{"x":0.332,"y":0.331},"t":20,"s":[226.102,138.397,0],"to":[-13.538,-32.678,0],"ti":[21.79,14.533,0]},{"t":28,"s":[168.17,69.295,0]}],"ix":2},"a":{"a":0,"k":[62.027,-12.355,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.164},"t":8,"s":[{"i":[[2.516,-6.249],[0,0],[6.903,0.419],[0,0],[-1.987,4.299],[0,0],[-7.209,-0.602],[0,0]],"o":[[0,0],[-1.967,2.18],[0,0],[-8.816,-1.347],[0,0],[2.17,-2.46],[0,0],[5.299,0.792]],"v":[[161.945,-34.476],[134.595,10.574],[120.346,16.613],[-37.025,-0.992],[-48.931,-13.662],[-19.761,-62.799],[-2.707,-68.708],[155.9,-48.303]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11.785,"s":[{"i":[[-0.62,-7.146],[24.266,-15.772],[6.897,0.475],[0,0],[-10.144,18.755],[0,0],[-7.201,-0.66],[0,0]],"o":[[0,0],[-1.611,6.106],[0,0],[-8.801,-1.419],[17.458,-31.917],[2.189,-2.441],[0,0],[5.29,0.835]],"v":[[176.594,-30.415],[136.76,12.668],[122.468,18.587],[-20.244,0.054],[-32.041,-12.709],[3.268,-65.026],[17.25,-70.48],[165.907,-44.98]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[7.268,-7.526],[11.042,-12.924],[6.905,0.324],[0,0],[-18.589,32.59],[0,0],[-7.213,-0.503],[0,0]],"o":[[0,0],[-6.535,7.649],[0,0],[-8.829,-1.226],[29.318,-51.402],[2.135,-2.488],[0,0],[5.306,0.719]],"v":[[208.562,-6.419],[189.704,17.731],[162.555,45.287],[24.586,36.525],[7.468,18.105],[44.979,-43.573],[61.315,-49.118],[208.152,-28.033]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[-1.674,-7.821],[5.846,-31.918],[6.855,0.89],[0,0],[-1.209,12.301],[0,0],[-7.147,-1.093],[0,0]],"o":[[0,0],[-2.049,7.934],[0,0],[-8.699,-1.946],[15.144,-51.127],[2.332,-2.305],[0,0],[5.23,1.152]],"v":[[191.552,-12.575],[190.205,69.374],[178.047,75.855],[41.045,65.235],[24.048,51.03],[24.881,-38.407],[37.635,-43.057],[176.588,-28.143]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[-6.996,-8.612],[-0.448,-28.68],[6.911,-0.175],[0,0],[1.699,15.655],[0,0],[-7.231,0.019],[0,0]],"o":[[13.328,16.408],[-1.152,6.252],[0,0],[-8.896,-0.586],[0.268,-41.886],[1.95,-2.636],[0,0],[5.345,0.335]],"v":[[163.235,-21.22],[199.589,61.124],[191.209,70.479],[50.449,70.568],[33.588,58.016],[-10.506,-34.863],[2.092,-37.752],[142.978,-32.696]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-7.256,-4.475],[-15.232,-36.815],[6.916,-0.045],[0,0],[-0.582,9.556],[1.574,4.124],[-7.233,-0.087],[0,0]],"o":[[0,0],[3.929,9.495],[0,0],[-8.887,-0.606],[2.621,-43.063],[-2.347,-6.149],[0,0],[5.341,0.35]],"v":[[131.416,-34.831],[185.982,24.651],[176.431,36.124],[18.207,41.687],[5.462,32.007],[-35.329,-37.09],[-19.1,-42.305],[114.097,-40.411]],"c":true}]},{"t":28,"s":[{"i":[[-7.583,-1.408],[-10.482,-12.74],[5.339,0.114],[0,0],[1.24,3.011],[3.16,1.374],[-5.191,0.695],[-29.909,-3.709]],"o":[[0,0],[-0.397,1.013],[-26.62,-2.211],[-6.606,-0.203],[-8.18,-13.777],[-1.646,-2.337],[5.772,-0.945],[3.215,0.189]],"v":[[92.11,-12.848],[153.114,11.129],[146.391,14.333],[35.34,12.786],[26.602,8.157],[0.215,-9.662],[8.591,-13.668],[76.61,-15.324]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[5,-14.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":8,"op":29,"st":-2,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Folder Far","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.29],"y":[0]},"t":0,"s":[-13]},{"i":{"x":[0.466],"y":[1]},"o":{"x":[0.553],"y":[0]},"t":12,"s":[5]},{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.506],"y":[0]},"t":29,"s":[-10]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":42,"s":[2]},{"t":50,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.29,"y":0},"t":0,"s":[174.274,223.861,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":10,"s":[234.274,131.861,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.4,"y":0},"t":27,"s":[234.274,189.861,0],"to":[0,0,0],"ti":[0,0,0]},{"t":41,"s":[234.274,153.861,0]}],"ix":2},"a":{"a":0,"k":[34.274,-21.139,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":0,"s":[40,0,100]},{"t":10,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":3,"s":[{"i":[[0,0],[-11.641,-0.603],[0,0],[-3.346,-2.144],[0,0],[-4.559,-0.236],[0,0],[0.179,-3.451],[0,0],[5.246,0],[0,0],[3.437,-10.77],[0,0]],"o":[[0.4,-7.669],[0,0],[4.559,0.236],[0,0],[3.346,2.144],[0,0],[5.239,0.272],[0,0],[-1.081,2.903],[0,0],[-14.946,0],[0,0],[0,0]],"v":[[-31.343,-23.38],[-9.543,-36.173],[52.203,-34.019],[64.458,-30.329],[77.186,-22.173],[89.441,-18.482],[151.238,-14.104],[159.509,-7.163],[158.328,-2.715],[147.415,-0.44],[-7.364,-2.697],[-36.675,13.091],[-53.574,55.488]],"c":true}]},{"i":{"x":0.5,"y":1},"o":{"x":0.4,"y":0},"t":20,"s":[{"i":[[0,0],[-11.045,0.001],[0,0],[-3.271,-2.83],[0,0],[-4.326,0],[0,0],[0,-4.971],[0,0],[4.971,0],[0,0],[2.948,-13.852],[0,0]],"o":[[0.002,-11.045],[0,0],[4.326,0],[0,0],[3.271,2.83],[0,0],[4.971,0],[0,0],[0,4.971],[0,0],[-14.162,0],[0,0],[0,0]],"v":[[-64.694,-61.634],[-44.692,-81.634],[23.101,-81.634],[34.879,-77.246],[46.309,-70.961],[58.086,-66.573],[123.293,-66.573],[133.279,-57.406],[132.954,-51.02],[121.043,-44.289],[-25.669,-44.289],[-54.929,-21.027],[-65.353,60.809]],"c":true}]},{"t":41,"s":[{"i":[[0,0],[-11.045,0.001],[0,0],[-3.271,-2.83],[0,0],[-4.326,0],[0,0],[0,-4.971],[0,0],[4.971,0],[0,0],[2.948,-13.852],[0,0]],"o":[[0.002,-11.045],[0,0],[4.326,0],[0,0],[3.271,2.83],[0,0],[4.971,0],[0,0],[0,4.971],[0,0],[-14.162,0],[0,0],[0,0]],"v":[[-64.723,-65.95],[-44.721,-85.95],[23.072,-85.95],[34.85,-81.562],[47.295,-70.794],[59.072,-66.406],[124.279,-66.406],[133.279,-57.406],[133.279,-49.95],[124.279,-40.95],[-22.434,-40.95],[-51.777,-17.195],[-64.73,43.672]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0}],"markers":[]} \ No newline at end of file From 4fa1d5462d1bc714eeec418a7e4c323fc31e5174 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 6 Apr 2023 20:10:57 +0400 Subject: [PATCH 08/57] Chat wallpaper improvements --- .../Themes/CustomWallpaperPicker.swift | 2 +- .../Themes/WallpaperGalleryToolbarNode.swift | 71 +++++++----- .../Themes/WallpaperOptionButtonNode.swift | 105 ++++-------------- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api12.swift | 24 ++-- submodules/TelegramApi/Sources/Api30.swift | 4 +- .../ApiUtils/TelegramMediaAction.swift | 4 +- .../SyncCore_TelegramMediaAction.swift | 11 +- .../TelegramEngine/Themes/ChatThemes.swift | 20 +++- .../Themes/TelegramEngineThemes.swift | 4 +- .../TelegramUI/Sources/ChatController.swift | 4 +- 11 files changed, 112 insertions(+), 139 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index 0dcb6dfb53e..a3831910cd0 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -196,7 +196,7 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE }).start() } -public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, peerId: PeerId, completion: @escaping () -> Void) { +public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightnessMultiplier: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) { let imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift index e29590d6571..13c47595c02 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift @@ -17,6 +17,39 @@ enum WallpaperGalleryToolbarDoneButtonType { case none } +final class WallpaperLightButtonBackgroundNode: ASDisplayNode { + private let backgroundNode: NavigationBackgroundNode + private let overlayNode: ASDisplayNode + private let lightNode: ASDisplayNode + + override init() { + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.01), enableBlur: true) + self.overlayNode = ASDisplayNode() + self.overlayNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.55) + self.overlayNode.layer.compositingFilter = "overlayBlendMode" + + self.lightNode = ASDisplayNode() + self.lightNode.backgroundColor = UIColor(rgb: 0xf2f2f2, alpha: 0.3) + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.overlayNode) + //self.addSubnode(self.lightNode) + } + + func updateLayout(size: CGSize) { + let frame = CGRect(origin: .zero, size: size) + self.backgroundNode.frame = frame + self.overlayNode.frame = frame + self.lightNode.frame = frame + + self.backgroundNode.update(size: size, transition: .immediate) + } +} + final class WallpaperGalleryToolbarNode: ASDisplayNode { private var theme: PresentationTheme private let strings: PresentationStrings @@ -33,11 +66,9 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { } private let doneButton = HighlightTrackingButtonNode() - private let doneButtonBackgroundNode: NavigationBackgroundNode + private let doneButtonBackgroundNode: WallpaperLightButtonBackgroundNode private let doneButtonTitleNode: ImmediateTextNode - private let doneButtonVibrancyView: UIVisualEffectView - private let doneButtonVibrancyTitleNode: ImmediateTextNode private let doneButtonSolidBackgroundNode: ASDisplayNode private let doneButtonSolidTitleNode: ImmediateTextNode @@ -51,25 +82,13 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { self.cancelButtonType = cancelButtonType self.doneButtonType = doneButtonType - self.doneButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75)) + self.doneButtonBackgroundNode = WallpaperLightButtonBackgroundNode() self.doneButtonBackgroundNode.cornerRadius = 14.0 - let blurEffect: UIBlurEffect - if #available(iOS 13.0, *) { - blurEffect = UIBlurEffect(style: .extraLight) - } else { - blurEffect = UIBlurEffect(style: .light) - } - self.doneButtonVibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect)) - self.doneButtonTitleNode = ImmediateTextNode() self.doneButtonTitleNode.displaysAsynchronously = false self.doneButtonTitleNode.isUserInteractionEnabled = false - self.doneButtonVibrancyTitleNode = ImmediateTextNode() - self.doneButtonVibrancyTitleNode.displaysAsynchronously = false - self.doneButtonVibrancyTitleNode.isUserInteractionEnabled = false - self.doneButtonSolidBackgroundNode = ASDisplayNode() self.doneButtonSolidBackgroundNode.alpha = 0.0 self.doneButtonSolidBackgroundNode.clipsToBounds = true @@ -87,8 +106,6 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { super.init() self.addSubnode(self.doneButtonBackgroundNode) - self.doneButtonVibrancyView.contentView.addSubnode(self.doneButtonVibrancyTitleNode) - self.doneButtonBackgroundNode.view.addSubview(self.doneButtonVibrancyView) self.doneButtonBackgroundNode.addSubnode(self.doneButtonTitleNode) self.addSubnode(self.doneButtonSolidBackgroundNode) @@ -109,8 +126,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { } else { strongSelf.doneButtonBackgroundNode.layer.removeAnimation(forKey: "opacity") strongSelf.doneButtonBackgroundNode.alpha = 0.55 - strongSelf.doneButtonVibrancyTitleNode.layer.removeAnimation(forKey: "opacity") - strongSelf.doneButtonVibrancyTitleNode.alpha = 0.55 + strongSelf.doneButtonTitleNode.layer.removeAnimation(forKey: "opacity") + strongSelf.doneButtonTitleNode.alpha = 0.55 } } else { if strongSelf.isSolid { @@ -121,8 +138,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { } else { strongSelf.doneButtonBackgroundNode.alpha = 1.0 strongSelf.doneButtonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) - strongSelf.doneButtonVibrancyTitleNode.alpha = 1.0 - strongSelf.doneButtonVibrancyTitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) + strongSelf.doneButtonTitleNode.alpha = 1.0 + strongSelf.doneButtonTitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) } } } @@ -146,7 +163,6 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { transition.updateAlpha(node: self.doneButtonBackgroundNode, alpha: isSolid ? 0.0 : 1.0) transition.updateAlpha(node: self.doneButtonSolidBackgroundNode, alpha: isSolid ? 1.0 : 0.0) transition.updateAlpha(node: self.doneButtonTitleNode, alpha: isSolid ? 0.0 : 1.0) - transition.updateAlpha(node: self.doneButtonVibrancyTitleNode, alpha: isSolid ? 0.0 : 1.0) transition.updateAlpha(node: self.doneButtonSolidTitleNode, alpha: isSolid ? 1.0 : 0.0) } @@ -167,8 +183,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { doneTitle = "" self.doneButton.isUserInteractionEnabled = false } - self.doneButtonTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: UIColor(rgb: 0x000000, alpha: 0.25)) - self.doneButtonVibrancyTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: .white) + self.doneButtonTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: .white) self.doneButtonSolidBackgroundNode.backgroundColor = theme.list.itemCheckColors.fillColor self.doneButtonSolidTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor) @@ -181,16 +196,12 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { let doneFrame = CGRect(origin: CGPoint(x: inset, y: 2.0), size: CGSize(width: size.width - inset * 2.0, height: buttonHeight)) self.doneButton.frame = doneFrame self.doneButtonBackgroundNode.frame = doneFrame - self.doneButtonBackgroundNode.update(size: doneFrame.size, cornerRadius: 14.0, transition: transition) - self.doneButtonVibrancyView.frame = self.doneButtonBackgroundNode.bounds + self.doneButtonBackgroundNode.updateLayout(size: doneFrame.size) self.doneButtonSolidBackgroundNode.frame = doneFrame let doneTitleSize = self.doneButtonTitleNode.updateLayout(doneFrame.size) self.doneButtonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((doneFrame.width - doneTitleSize.width) / 2.0), y: floorToScreenPixels((doneFrame.height - doneTitleSize.height) / 2.0)), size: doneTitleSize) - let _ = self.doneButtonVibrancyTitleNode.updateLayout(doneFrame.size) - self.doneButtonVibrancyTitleNode.frame = self.doneButtonTitleNode.frame - let _ = self.doneButtonSolidTitleNode.updateLayout(doneFrame.size) self.doneButtonSolidTitleNode.frame = self.doneButtonTitleNode.frame.offsetBy(dx: doneFrame.minX, dy: doneFrame.minY) } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 202496bd49a..5ccd6243ded 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -42,41 +42,25 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { private let content: Content - private let backgroundNode: NavigationBackgroundNode - private let vibrancyView: UIVisualEffectView - + private let backgroundNode: WallpaperLightButtonBackgroundNode + private let iconNode: ASImageNode - private let darkIconNode: ASImageNode private let textNode: ImmediateTextNode - private let darkTextNode: ImmediateTextNode func setIcon(_ image: UIImage?) { self.iconNode.image = generateTintedImage(image: image, color: .white) - self.darkIconNode.image = generateTintedImage(image: image, color: UIColor(rgb: 0x000000, alpha: 0.25)) } init(content: Content) { self.content = content - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75)) - - let blurEffect: UIBlurEffect - if #available(iOS 13.0, *) { - blurEffect = UIBlurEffect(style: .extraLight) - } else { - blurEffect = UIBlurEffect(style: .light) - } - self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect)) + self.backgroundNode = WallpaperLightButtonBackgroundNode() self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false self.iconNode.contentMode = .center - self.darkIconNode = ASImageNode() - self.darkIconNode.displaysAsynchronously = false - self.darkIconNode.contentMode = .center - var title: String switch content { case let .text(text): @@ -84,23 +68,16 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { case let .icon(icon, _): title = "" self.iconNode.image = generateTintedImage(image: icon, color: .white) - self.darkIconNode.image = generateTintedImage(image: icon, color: UIColor(rgb: 0x000000, alpha: 0.25)) } self.textNode = ImmediateTextNode() self.textNode.attributedText = NSAttributedString(string: title, font: Font.semibold(15.0), textColor: .white) - self.darkTextNode = ImmediateTextNode() - self.darkTextNode.attributedText = NSAttributedString(string: title, font: Font.semibold(15.0), textColor: UIColor(rgb: 0x000000, alpha: 0.25)) - super.init() self.addSubnode(self.backgroundNode) - self.vibrancyView.contentView.addSubnode(self.iconNode) - self.vibrancyView.contentView.addSubnode(self.textNode) - self.backgroundNode.view.addSubview(self.vibrancyView) - self.backgroundNode.addSubnode(self.darkIconNode) - self.backgroundNode.addSubnode(self.darkTextNode) + self.addSubnode(self.iconNode) + self.addSubnode(self.textNode) self.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { @@ -117,11 +94,11 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { didSet { - if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) { - self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate) - } else { - self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate) - } +// if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) { +// self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate) +// } else { +// self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate) +// } } } @@ -130,7 +107,6 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { switch self.content { case .text: let size = self.textNode.updateLayout(constrainedSize) - let _ = self.darkTextNode.updateLayout(constrainedSize) self.textSize = size return CGSize(width: ceil(size.width) + 16.0, height: 28.0) case let .icon(_, size): @@ -143,15 +119,13 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { let size = self.bounds.size self.backgroundNode.frame = self.bounds - self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: self.bounds.size.height / 2.0, transition: .immediate) - self.vibrancyView.frame = self.bounds + self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) + self.backgroundNode.cornerRadius = size.height / 2.0 self.iconNode.frame = self.bounds - self.darkIconNode.frame = self.bounds if let textSize = self.textSize { self.textNode.frame = CGRect(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0), width: textSize.width, height: textSize.height) - self.darkTextNode.frame = self.textNode.frame } } } @@ -159,15 +133,11 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { private let backgroundNode: NavigationBackgroundNode - private let vibrancyView: UIVisualEffectView private let checkNode: CheckNode - private let darkCheckNode: CheckNode - private let colorNode: ASImageNode private let textNode: ImmediateTextNode - private let darkTextNode: ImmediateTextNode private var textSize: CGSize? @@ -189,14 +159,12 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { self._value = .colors(newValue, colors) } self.checkNode.setSelected(newValue, animated: false) - self.darkCheckNode.setSelected(newValue, animated: false) } } var title: String { didSet { self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: .white) - self.darkTextNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: UIColor(rgb: 0x000000, alpha: 0.25)) } } @@ -204,62 +172,38 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { self._value = value self.title = title - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xffffff, alpha: 0.4)) + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.01)) self.backgroundNode.cornerRadius = 14.0 - - let blurEffect: UIBlurEffect - if #available(iOS 13.0, *) { - blurEffect = UIBlurEffect(style: .extraLight) - } else { - blurEffect = UIBlurEffect(style: .light) - } - self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect)) - - let darkColor = UIColor(rgb: 0x000000, alpha: 0.25) - + self.checkNode = CheckNode(theme: CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: false, borderWidth: 1.5)) self.checkNode.isUserInteractionEnabled = false - self.darkCheckNode = CheckNode(theme: CheckNodeTheme(backgroundColor: darkColor, strokeColor: .clear, borderColor: darkColor, overlayBorder: false, hasInset: false, hasShadow: false, borderWidth: 1.5)) - self.darkCheckNode.isUserInteractionEnabled = false - self.colorNode = ASImageNode() self.textNode = ImmediateTextNode() self.textNode.displaysAsynchronously = false self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: .white) - - self.darkTextNode = ImmediateTextNode() - self.darkTextNode.displaysAsynchronously = false - self.darkTextNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: UIColor(rgb: 0x000000, alpha: 0.25)) - + super.init() switch value { case let .check(selected): self.checkNode.isHidden = false - self.darkCheckNode.isHidden = false self.colorNode.isHidden = true self.checkNode.selected = selected - self.darkCheckNode.selected = selected case let .color(_, color): self.checkNode.isHidden = true - self.darkCheckNode.isHidden = true self.colorNode.isHidden = false self.colorNode.image = generateFilledCircleImage(diameter: 18.0, color: color) case let .colors(_, colors): self.checkNode.isHidden = true - self.darkCheckNode.isHidden = true self.colorNode.isHidden = false self.colorNode.image = generateColorsImage(diameter: 18.0, colors: colors) } self.addSubnode(self.backgroundNode) - self.vibrancyView.contentView.addSubnode(self.checkNode) - self.vibrancyView.contentView.addSubnode(self.textNode) - self.backgroundNode.view.addSubview(self.vibrancyView) - self.addSubnode(self.darkCheckNode) - self.addSubnode(self.darkTextNode) + self.addSubnode(self.checkNode) + self.addSubnode(self.textNode) self.addSubnode(self.colorNode) self.highligthedChanged = { [weak self] highlighted in @@ -283,11 +227,11 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { didSet { - if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) { - self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate) - } else { - self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate) - } +// if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) { +// self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate) +// } else { +// self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate) +// } } } @@ -357,7 +301,6 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { self._value = .colors(selected, colors) } self.checkNode.setSelected(selected, animated: animated) - self.darkCheckNode.setSelected(selected, animated: animated) } func setEnabled(_ enabled: Bool) { @@ -371,7 +314,6 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { override func measure(_ constrainedSize: CGSize) -> CGSize { let size = self.textNode.updateLayout(constrainedSize) - let _ = self.darkTextNode.updateLayout(constrainedSize) self.textSize = size return CGSize(width: ceil(size.width) + 48.0, height: 30.0) } @@ -381,7 +323,6 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { self.backgroundNode.frame = self.bounds self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: 15.0, transition: .immediate) - self.vibrancyView.frame = self.bounds guard let _ = self.textSize else { return @@ -392,12 +333,10 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { let checkSize = CGSize(width: 18.0, height: 18.0) let checkFrame = CGRect(origin: CGPoint(x: padding, y: padding), size: checkSize) self.checkNode.frame = checkFrame - self.darkCheckNode.frame = checkFrame self.colorNode.frame = checkFrame if let textSize = self.textSize { self.textNode.frame = CGRect(x: max(padding + checkSize.width + spacing, padding + checkSize.width + floor((self.bounds.width - padding - checkSize.width - textSize.width) / 2.0) - 2.0), y: floorToScreenPixels((self.bounds.height - textSize.height) / 2.0), width: textSize.width, height: textSize.height) - self.darkTextNode.frame = self.textNode.frame } } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 6822745a8e1..bd9372bcd04 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -479,7 +479,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[-1136350937] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } - dict[-632006598] = { return Api.MessageAction.parse_messageActionSetSameChatWallPaper($0) } + dict[-1065845395] = { return Api.MessageAction.parse_messageActionSetSameChatWallPaper($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index 4e3c6116ff3..55ad86a29fa 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -263,7 +263,7 @@ public extension Api { case messageActionSetChatTheme(emoticon: String) case messageActionSetChatWallPaper(wallpaper: Api.WallPaper) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) - case messageActionSetSameChatWallPaper + case messageActionSetSameChatWallPaper(wallpaper: Api.WallPaper) case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) @@ -522,11 +522,11 @@ public extension Api { serializeInt32(period, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)} break - case .messageActionSetSameChatWallPaper: + case .messageActionSetSameChatWallPaper(let wallpaper): if boxed { - buffer.appendInt32(-632006598) + buffer.appendInt32(-1065845395) } - + wallpaper.serialize(buffer, true) break case .messageActionSuggestProfilePhoto(let photo): if boxed { @@ -637,8 +637,8 @@ public extension Api { return ("messageActionSetChatWallPaper", [("wallpaper", wallpaper as Any)]) case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) - case .messageActionSetSameChatWallPaper: - return ("messageActionSetSameChatWallPaper", []) + case .messageActionSetSameChatWallPaper(let wallpaper): + return ("messageActionSetSameChatWallPaper", [("wallpaper", wallpaper as Any)]) case .messageActionSuggestProfilePhoto(let photo): return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): @@ -1092,7 +1092,17 @@ public extension Api { } } public static func parse_messageActionSetSameChatWallPaper(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionSetSameChatWallPaper + var _1: Api.WallPaper? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.WallPaper + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSetSameChatWallPaper(wallpaper: _1!) + } + else { + return nil + } } public static func parse_messageActionSuggestProfilePhoto(_ reader: BufferReader) -> MessageAction? { var _1: Api.Photo? diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index 697f9d22ba3..66622b9ad09 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -6884,11 +6884,11 @@ public extension Api.functions.messages { public extension Api.functions.messages { static func setChatWallPaper(flags: Int32, peer: Api.InputPeer, wallpaper: Api.InputWallPaper?, settings: Api.WallPaperSettings?, id: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-609568219) + buffer.appendInt32(-1879389471) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {wallpaper!.serialize(buffer, true)} - if Int(flags) & Int(1 << 0) != 0 {settings!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {settings!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(id!, buffer: buffer, boxed: false)} return (FunctionDescription(name: "messages.setChatWallPaper", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("wallpaper", String(describing: wallpaper)), ("settings", String(describing: settings)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index e8972b5cf69..fef8fb0c465 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -110,8 +110,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerId: peer.peerId)) case let .messageActionSetChatWallPaper(wallpaper): return TelegramMediaAction(action: .setChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper))) - case .messageActionSetSameChatWallPaper: - return TelegramMediaAction(action: .setSameChatWallpaper) + case let .messageActionSetSameChatWallPaper(wallpaper): + return TelegramMediaAction(action: .setSameChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper))) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 22442aba6af..d8c8d6d550c 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -102,7 +102,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case attachMenuBotAllowed case requestedPeer(buttonId: Int32, peerId: PeerId) case setChatWallpaper(wallpaper: TelegramWallpaper) - case setSameChatWallpaper + case setSameChatWallpaper(wallpaper: TelegramWallpaper) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -190,7 +190,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .unknown } case 34: - self = .setSameChatWallpaper + if let wallpaper = decoder.decode(TelegramWallpaperNativeCodable.self, forKey: "wallpaper")?.value { + self = .setSameChatWallpaper(wallpaper: wallpaper) + } else { + self = .unknown + } default: self = .unknown } @@ -355,8 +359,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case let .setChatWallpaper(wallpaper): encoder.encodeInt32(33, forKey: "_rawValue") encoder.encode(TelegramWallpaperNativeCodable(wallpaper), forKey: "wallpaper") - case .setSameChatWallpaper: + case let .setSameChatWallpaper(wallpaper): encoder.encodeInt32(34, forKey: "_rawValue") + encoder.encode(TelegramWallpaperNativeCodable(wallpaper), forKey: "wallpaper") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index 4d7c7609cfa..4a78cf87ad4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -134,15 +134,17 @@ func _internal_setChatWallpaper(account: Account, peerId: PeerId, wallpaper: Tel } }) + var flags: Int32 = 0 + var inputWallpaper: Api.InputWallPaper? var inputSettings: Api.WallPaperSettings? if let inputWallpaperAndInputSettings = wallpaper?.apiInputWallpaperAndSettings { + flags |= 1 << 0 + inputWallpaper = inputWallpaperAndInputSettings.0 inputSettings = inputWallpaperAndInputSettings.1 } - - let flags: Int32 = 1 << 0 - return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper ?? .inputWallPaperNoFile(id: 0), settings: inputSettings ?? apiWallpaperSettings(WallpaperSettings()), id: nil)) + return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper, settings: inputSettings, id: nil)) |> `catch` { error in return .complete() } @@ -158,7 +160,7 @@ public enum SetExistingChatWallpaperError { case generic } -func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId) -> Signal { +func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId, wallpaper: TelegramWallpaper?) -> Signal { return account.postbox.transaction { transaction -> Peer? in if let peer = transaction.getPeer(messageId.peerId), let message = transaction.getMessage(messageId) { if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .setChatWallpaper(wallpaper) = action.action { @@ -180,8 +182,14 @@ func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId) guard let peer = peer, let inputPeer = apiInputPeer(peer) else { return .complete() } - let flags: Int32 = 1 << 1 - return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: nil, settings: nil, id: messageId.id)) + var flags: Int32 = 1 << 1 + + var inputSettings: Api.WallPaperSettings? + if let inputWallpaperAndInputSettings = wallpaper?.apiInputWallpaperAndSettings { + flags |= 1 << 2 + inputSettings = inputWallpaperAndInputSettings.1 + } + return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: nil, settings: inputSettings, id: messageId.id)) |> `catch` { _ -> Signal in return .fail(.generic) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift index 7cfd04ef326..d444f1edcee 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift @@ -21,8 +21,8 @@ public extension TelegramEngine { return _internal_setChatWallpaper(account: self.account, peerId: peerId, wallpaper: wallpaper) } - public func setExistingChatWallpaper(messageId: MessageId) -> Signal { - return _internal_setExistingChatWallpaper(account: self.account, messageId: messageId) + public func setExistingChatWallpaper(messageId: MessageId, wallpaper: TelegramWallpaper?) -> Signal { + return _internal_setExistingChatWallpaper(account: self.account, messageId: messageId, wallpaper: wallpaper) } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index bbd85374e45..f0772f42296 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -846,7 +846,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.dismissInput() let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true)) wallpaperPreviewController.apply = { wallpaper, options, _ in - let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id) + let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, wallpaper: nil) |> deliverOnMainQueue).start(completed: { [weak wallpaperPreviewController] in wallpaperPreviewController?.dismiss() }) @@ -18537,7 +18537,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G controller.navigationPresentation = .modal controller.apply = { [weak self] wallpaper, options, cropRect in if let strongSelf = self { - uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, peerId: peerId, completion: { + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightnessMultiplier: nil, peerId: peerId, completion: { dismissControllers() }) } From a74dc88c79a6ae6e34e275754016853b0b49794a Mon Sep 17 00:00:00 2001 From: Mike Renoir <> Date: Fri, 7 Apr 2023 13:01:55 +0400 Subject: [PATCH 09/57] api fixes --- .../Sources/State/AccountStateManagementUtils.swift | 4 ++-- .../Sources/TelegramEngine/Themes/ChatThemes.swift | 12 ++++++------ .../TelegramEngine/Themes/TelegramEngineThemes.swift | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index b2b16fb8766..8c8bd961310 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -3405,8 +3405,8 @@ func replayFinalState( for (space, _) in holesAtHistoryStart { transaction.removeHole(peerId: chatPeerId, threadId: nil, namespace: Namespaces.Message.Cloud, space: space, range: 1 ... id.id) } - case let .setChatWallpaper(wallpaper): - if chatPeerId == accountPeerId { + case let .setChatWallpaper(wallpaper), let .setSameChatWallpaper(wallpaper): + if message.authorId == accountPeerId { transaction.updatePeerCachedData(peerIds: [message.id.peerId], update: { peerId, current in var current = current if current == nil { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index 4a78cf87ad4..f7be6abafda 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -140,11 +140,11 @@ func _internal_setChatWallpaper(account: Account, peerId: PeerId, wallpaper: Tel var inputSettings: Api.WallPaperSettings? if let inputWallpaperAndInputSettings = wallpaper?.apiInputWallpaperAndSettings { flags |= 1 << 0 - + flags |= 1 << 2 inputWallpaper = inputWallpaperAndInputSettings.0 inputSettings = inputWallpaperAndInputSettings.1 } - return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper, settings: inputSettings, id: nil)) + return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper, settings: inputSettings, id: nil), automaticFloodWait: false) |> `catch` { error in return .complete() } @@ -160,7 +160,7 @@ public enum SetExistingChatWallpaperError { case generic } -func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId, wallpaper: TelegramWallpaper?) -> Signal { +func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId, settings: WallpaperSettings?) -> Signal { return account.postbox.transaction { transaction -> Peer? in if let peer = transaction.getPeer(messageId.peerId), let message = transaction.getMessage(messageId) { if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .setChatWallpaper(wallpaper) = action.action { @@ -185,11 +185,11 @@ func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId, var flags: Int32 = 1 << 1 var inputSettings: Api.WallPaperSettings? - if let inputWallpaperAndInputSettings = wallpaper?.apiInputWallpaperAndSettings { + if let settings = settings { flags |= 1 << 2 - inputSettings = inputWallpaperAndInputSettings.1 + inputSettings = apiWallpaperSettings(settings) } - return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: nil, settings: inputSettings, id: messageId.id)) + return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: nil, settings: inputSettings, id: messageId.id), automaticFloodWait: false) |> `catch` { _ -> Signal in return .fail(.generic) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift index d444f1edcee..3e60af86f6c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift @@ -21,8 +21,8 @@ public extension TelegramEngine { return _internal_setChatWallpaper(account: self.account, peerId: peerId, wallpaper: wallpaper) } - public func setExistingChatWallpaper(messageId: MessageId, wallpaper: TelegramWallpaper?) -> Signal { - return _internal_setExistingChatWallpaper(account: self.account, messageId: messageId, wallpaper: wallpaper) + public func setExistingChatWallpaper(messageId: MessageId, settings: WallpaperSettings?) -> Signal { + return _internal_setExistingChatWallpaper(account: self.account, messageId: messageId, settings: settings) } } } From 2b373255050b5e3a1e6a09e481b5d4951b351f19 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 7 Apr 2023 14:18:41 +0400 Subject: [PATCH 10/57] Fix colors --- submodules/TelegramUI/Sources/ChatController.swift | 2 +- .../TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift | 2 +- submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f0772f42296..bfbc4c4d9ed 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -846,7 +846,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.dismissInput() let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true)) wallpaperPreviewController.apply = { wallpaper, options, _ in - let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, wallpaper: nil) + let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, settings: nil) |> deliverOnMainQueue).start(completed: { [weak wallpaperPreviewController] in wallpaperPreviewController?.dismiss() }) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 4debf79fd8c..5cf74c2ebcd 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1475,7 +1475,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper) + let foregroundColor: UIColor = .clear// = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper) let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper) strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: animationNodeFrame.size, enableEffect: item.context.sharedContext.energyUsageSettings.fullTranslucency, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)) strongSelf.placeholderNode.frame = animationNodeFrame diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 070c7142e4a..45c05615fdc 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -898,7 +898,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper) + let foregroundColor: UIColor = .clear//bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper) let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper) let placeholderFrame = updatedImageFrame.insetBy(dx: innerImageInset, dy: innerImageInset) From 6dc31ae1485a068c546897a62f22166ac87574fe Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 7 Apr 2023 15:28:18 +0400 Subject: [PATCH 11/57] Update counters --- .../Sources/ChatFolderLinkPreviewScreen.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift index b38ce65ad08..848ac5fe1e8 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift @@ -386,6 +386,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { let titleString: String var allChatsAdded = false + var canAddChatCount = 0 if case .linkList = component.subject { //TODO:localize titleString = "Share Folder" @@ -397,13 +398,14 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { if linkContents.alreadyMemberPeerIds == Set(linkContents.peers.map(\.id)) { allChatsAdded = true } + canAddChatCount = linkContents.peers.map(\.id).count - linkContents.alreadyMemberPeerIds.count if allChatsAdded { titleString = "Add Folder" - } else if linkContents.peers.count == 1 { - titleString = "Add \(linkContents.peers.count) chat" + } else if canAddChatCount == 1 { + titleString = "Add \(canAddChatCount) chat" } else { - titleString = "Add \(linkContents.peers.count) chats" + titleString = "Add \(canAddChatCount) chats" } } else { titleString = "Add Folder" @@ -433,8 +435,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { var topBadge: String? if case .linkList = component.subject { } else if case .remove = component.subject { - } else if !allChatsAdded, let linkContents = component.linkContents, linkContents.localFilterId != nil { - topBadge = "+\(linkContents.peers.count)" + } else if !allChatsAdded, let linkContents = component.linkContents, linkContents.localFilterId != nil, canAddChatCount != 0 { + topBadge = "+\(canAddChatCount)" } let topIconSize = self.topIcon.update( @@ -472,10 +474,10 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { text = "Do you want to add a new chat folder\nand join its groups and channels?" } else { let chatCountString: String - if linkContents.peers.count == 1 { + if canAddChatCount == 1 { chatCountString = "1 chat" } else { - chatCountString = "\(linkContents.peers.count) chats" + chatCountString = "\(canAddChatCount) chats" } if let title = linkContents.title { text = "Do you want to add **\(chatCountString)** to the\nfolder **\(title)**?" @@ -934,7 +936,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { actionButtonBadge = 0 actionButtonTitle = "OK" } else if let linkContents = component.linkContents { - actionButtonBadge = self.selectedItems.count + actionButtonBadge = max(0, self.selectedItems.count - (linkContents.peers.count - canAddChatCount)) if linkContents.localFilterId != nil { if self.selectedItems.isEmpty { actionButtonTitle = "Do Not Join Any Chats" From 97e0e96552fafcdde2f278eaf4fabb7c75fbdc96 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 7 Apr 2023 17:39:24 +0400 Subject: [PATCH 12/57] Chat wallpaper improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + .../Sources/AccountContext.swift | 1 + submodules/Display/Source/NavigationBar.swift | 10 +- .../GalleryUI/Sources/GalleryController.swift | 4 +- submodules/ImageBlur/Sources/ImageBlur.swift | 1 + .../Themes/CustomWallpaperPicker.swift | 28 +- .../ThemeColorsGridControllerNode.swift | 2 +- .../Sources/Themes/ThemeGridController.swift | 8 +- .../Themes/ThemeSettingsController.swift | 38 +-- .../Themes/WallpaperGalleryController.swift | 6 +- .../Sources/Themes/WallpaperGalleryItem.swift | 120 ++++++-- .../Themes/WallpaperGalleryToolbarNode.swift | 33 -- .../Themes/WallpaperOptionButtonNode.swift | 238 +++++++++++++-- .../Sources/PresentationData.swift | 18 +- .../Contents.json | 12 + .../brightness_max.pdf | Bin 0 -> 3401 bytes .../Contents.json | 12 + .../brightness_min.pdf | Bin 0 -> 4376 bytes .../TelegramUI/Sources/ChatController.swift | 44 ++- .../Sources/FetchCachedRepresentations.swift | 4 +- .../Sources/SharedAccountContext.swift | 14 + .../Sources/MediaDisplaySettings.swift | 50 +++ .../Sources/PostboxKeys.swift | 2 + .../WebAppOpenConfirmationController.swift | 287 ++++++++++++++++++ 24 files changed, 801 insertions(+), 134 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMax.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMax.imageset/brightness_max.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/brightness_min.pdf create mode 100644 submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift create mode 100644 submodules/WebUI/Sources/WebAppOpenConfirmationController.swift diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 8c6579dc491..5d55c6e9231 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9158,3 +9158,6 @@ Sorry for the inconvenience."; "Conversation.Theme.SetCustomColor" = "Set Custom"; "Appearance.ShowNextMediaOnTap" = "Show Next Media on Tap"; + +"WebApp.LaunchMoreInfo" = "More about this bot"; +"WebApp.LaunchConfirmation" = "To launch this web app, you will connect to its website."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 3173913004d..0869ada5a67 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -756,6 +756,7 @@ public protocol SharedAccountContext: AnyObject { var currentInAppNotificationSettings: Atomic { get } var currentMediaInputSettings: Atomic { get } var currentStickerSettings: Atomic { get } + var currentMediaDisplaySettings: Atomic { get } var energyUsageSettings: EnergyUsageSettings { get } diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 234eb1e7683..92c5873c066 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -138,6 +138,7 @@ public final class NavigationBackgroundNode: ASDisplayNode { private var _color: UIColor private var enableBlur: Bool + private var enableSaturation: Bool public var effectView: UIVisualEffectView? private let backgroundNode: ASDisplayNode @@ -152,9 +153,10 @@ public final class NavigationBackgroundNode: ASDisplayNode { } } - public init(color: UIColor, enableBlur: Bool = true) { + public init(color: UIColor, enableBlur: Bool = true, enableSaturation: Bool = true) { self._color = .clear self.enableBlur = enableBlur + self.enableSaturation = enableSaturation self.backgroundNode = ASDisplayNode() @@ -195,10 +197,12 @@ public final class NavigationBackgroundNode: ASDisplayNode { if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters { sublayer.backgroundColor = nil sublayer.isOpaque = false - let allowedKeys: [String] = [ - "colorSaturate", + var allowedKeys: [String] = [ "gaussianBlur" ] + if self.enableSaturation { + allowedKeys.append("colorSaturate") + } sublayer.filters = filters.filter { filter in guard let filter = filter as? NSObject else { return true diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index b1fa7a8a987..5823fa9537f 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -1211,7 +1211,9 @@ public class GalleryController: ViewController, StandalonePresentableController, }) } }) - self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction) + + let disableTapNavigation = !(self.context.sharedContext.currentMediaDisplaySettings.with { $0 }.showNextMediaOnTap) + self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction, disableTapNavigation: disableTapNavigation) self.displayNodeDidLoad() self.galleryNode.statusBar = self.statusBar diff --git a/submodules/ImageBlur/Sources/ImageBlur.swift b/submodules/ImageBlur/Sources/ImageBlur.swift index 05fb8da1bed..1340bddc0b2 100644 --- a/submodules/ImageBlur/Sources/ImageBlur.swift +++ b/submodules/ImageBlur/Sources/ImageBlur.swift @@ -39,6 +39,7 @@ public func blurredImage(_ image: UIImage, radius: CGFloat, iterations: Int = 3) let source = CFDataGetBytePtr(providerData) memcpy(inBuffer.data, source, bytes) + for _ in 0 ..< iterations { vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, tempData, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend)) diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index a3831910cd0..2edaaea2d2b 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -25,9 +25,9 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V controller.selectionBlock = { [weak legacyController] asset, _ in if let asset = asset { let controller = WallpaperGalleryController(context: context, source: .asset(asset.backingAsset)) - controller.apply = { [weak legacyController, weak controller] wallpaper, mode, cropRect in + controller.apply = { [weak legacyController, weak controller] wallpaper, mode, cropRect, brightness in if let legacyController = legacyController, let controller = controller { - uploadCustomWallpaper(context: context, wallpaper: wallpaper, mode: mode, cropRect: cropRect, completion: { [weak legacyController, weak controller] in + uploadCustomWallpaper(context: context, wallpaper: wallpaper, mode: mode, cropRect: cropRect, brightness: brightness, completion: { [weak legacyController, weak controller] in if let legacyController = legacyController, let controller = controller { legacyController.dismiss() controller.dismiss(forceAway: true) @@ -47,7 +47,7 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V }) } -func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, completion: @escaping () -> Void) { +func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightness: CGFloat?, completion: @escaping () -> Void) { let imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): @@ -196,7 +196,7 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE }).start() } -public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightnessMultiplier: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) { +public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) { let imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): @@ -276,7 +276,25 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) if mode.contains(.blur) { - croppedImage = blurredImage(croppedImage, radius: 20.0)! + croppedImage = blurredImage(croppedImage, radius: 30.0)! + } + + if let brightness, abs(brightness) > 0.01 { + if let updatedImage = generateImage(croppedImage.size, contextGenerator: { size, context in + let bounds = CGRect(origin: .zero, size: size) + if let cgImage = croppedImage.cgImage { + context.draw(cgImage, in: bounds) + } + if brightness > 0.0 { + context.setFillColor(UIColor(rgb: 0xffffff, alpha: brightness).cgColor) + context.setBlendMode(.overlay) + } else { + context.setFillColor(UIColor(rgb: 0x000000, alpha: brightness * -1.0).cgColor) + } + context.fill(bounds) + }) { + croppedImage = updatedImage + } } let thumbnailDimensions = finalCropRect.size.fitted(CGSize(width: 320.0, height: 320.0)) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift index 94bbdcb6d1c..5ac533d4c97 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift @@ -152,7 +152,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { let wallpapers = entries.map { $0.wallpaper } let controller = WallpaperGalleryController(context: context, source: .list(wallpapers: wallpapers, central: wallpaper, type: .colors), mode: strongSelf.controller?.mode.galleryMode ?? .default) controller.navigationPresentation = .modal - controller.apply = { [weak self] wallpaper, _, _ in + controller.apply = { [weak self] wallpaper, _, _, _ in if let strongSelf = self, let mode = strongSelf.controller?.mode, case let .peer(peer) = mode, case let .wallpaper(wallpaperValue, _) = wallpaper { let _ = (strongSelf.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: wallpaperValue) |> deliverOnMainQueue).start(completed: { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index 14658adb61b..cef1f6070ca 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -120,9 +120,9 @@ public final class ThemeGridController: ViewController { self.displayNode = ThemeGridControllerNode(context: self.context, presentationData: self.presentationData, presentPreviewController: { [weak self] source in if let strongSelf = self { let controller = WallpaperGalleryController(context: strongSelf.context, source: source) - controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in + controller.apply = { [weak self, weak controller] wallpaper, options, cropRect, brightness in if let strongSelf = self { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak self, weak controller] in + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in if let strongSelf = self { strongSelf.deactivateSearch(animated: false) strongSelf.controllerNode.scrollToTop(animated: false) @@ -148,9 +148,9 @@ public final class ThemeGridController: ViewController { return } let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset)) - controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in + controller.apply = { [weak self, weak controller] wallpaper, options, cropRect, brightness in if let strongSelf = self, let controller = controller { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak controller] in + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, completion: { [weak controller] in if let controller = controller { controller.dismiss(forceAway: true) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 93eab9cfd5c..0e3fd9f8945 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -58,13 +58,13 @@ private final class ThemeSettingsControllerArguments { let openBubbleSettings: () -> Void let openPowerSavingSettings: () -> Void let openStickersAndEmoji: () -> Void - let disableAnimations: (Bool) -> Void + let toggleShowNextMediaOnTap: (Bool) -> Void let selectAppIcon: (PresentationAppIcon) -> Void let editTheme: (PresentationCloudTheme) -> Void let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void - init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) { + init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, toggleShowNextMediaOnTap: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) { self.context = context self.selectTheme = selectTheme self.openThemeSettings = openThemeSettings @@ -77,7 +77,7 @@ private final class ThemeSettingsControllerArguments { self.openBubbleSettings = openBubbleSettings self.openPowerSavingSettings = openPowerSavingSettings self.openStickersAndEmoji = openStickersAndEmoji - self.disableAnimations = disableAnimations + self.toggleShowNextMediaOnTap = toggleShowNextMediaOnTap self.selectAppIcon = selectAppIcon self.editTheme = editTheme self.themeContextAction = themeContextAction @@ -128,7 +128,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { case powerSaving case stickersAndEmoji case otherHeader(PresentationTheme, String) - case animations(PresentationTheme, String, Bool) + case showNextMediaOnTap(PresentationTheme, String, Bool) case animationsInfo(PresentationTheme, String) var section: ItemListSectionId { @@ -143,7 +143,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return ThemeSettingsControllerSection.icon.rawValue case .powerSaving, .stickersAndEmoji: return ThemeSettingsControllerSection.message.rawValue - case .otherHeader, .animations, .animationsInfo: + case .otherHeader, .showNextMediaOnTap, .animationsInfo: return ThemeSettingsControllerSection.other.rawValue } } @@ -178,7 +178,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return 12 case .otherHeader: return 13 - case .animations: + case .showNextMediaOnTap: return 14 case .animationsInfo: return 15 @@ -271,8 +271,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } else { return false } - case let .animations(lhsTheme, lhsTitle, lhsValue): - if case let .animations(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + case let .showNextMediaOnTap(lhsTheme, lhsTitle, lhsValue): + if case let .showNextMediaOnTap(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { return true } else { return false @@ -343,9 +343,9 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { }) case let .otherHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) - case let .animations(_, title, value): + case let .showNextMediaOnTap(_, title, value): return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in - arguments.disableAnimations(value) + arguments.toggleShowNextMediaOnTap(value) }, tag: ThemeSettingsEntryTag.animations) case let .animationsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) @@ -353,7 +353,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } } -private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]]) -> [ThemeSettingsControllerEntry] { +private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]]) -> [ThemeSettingsControllerEntry] { var entries: [ThemeSettingsControllerEntry] = [] let strings = presentationData.strings @@ -404,8 +404,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData, } entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased())) - entries.append(.animations(presentationData.theme, strings.Appearance_ReduceMotion, presentationData.reduceMotion)) - entries.append(.animationsInfo(presentationData.theme, strings.Appearance_ReduceMotionInfo)) + entries.append(.showNextMediaOnTap(presentationData.theme, strings.Appearance_ShowNextMediaOnTap, mediaSettings.showNextMediaOnTap)) return entries } @@ -522,9 +521,9 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The pushControllerImpl?(installedStickerPacksController(context: context, mode: .general, archivedPacks: archivedStickerPacks, updatedPacks: { _ in })) }) - }, disableAnimations: { reduceMotion in - let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in - return current.withUpdatedReduceMotion(reduceMotion) + }, toggleShowNextMediaOnTap: { value in + let _ = updateMediaDisplaySettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + return current.withUpdatedShowNextMediaOnTap(value) }).start() }, selectAppIcon: { icon in let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) @@ -1000,10 +999,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }) }) - let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId)) + let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId)) |> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers, peerView -> (ItemListControllerState, (ItemListNodeState, Any)) in let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings - + let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings + let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false let themeReference: PresentationThemeReference @@ -1041,7 +1041,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The chatThemes.insert(.builtin(.dayClassic), at: 0) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) return (controllerState, (listState, arguments)) } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift index e0e357ec396..3e8880194c1 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift @@ -176,7 +176,7 @@ public class WallpaperGalleryController: ViewController { private let context: AccountContext private let source: WallpaperListSource private let mode: Mode - public var apply: ((WallpaperGalleryEntry, WallpaperPresentationOptions, CGRect?) -> Void)? + public var apply: ((WallpaperGalleryEntry, WallpaperPresentationOptions, CGRect?, CGFloat?) -> Void)? private let _ready = Promise() override public var ready: Promise { @@ -453,7 +453,7 @@ public class WallpaperGalleryController: ViewController { let entry = strongSelf.entries[centralItemNode.index] if case .peer = strongSelf.mode { - strongSelf.apply?(entry, options, centralItemNode.cropRect) + strongSelf.apply?(entry, options, centralItemNode.cropRect, centralItemNode.brightness) return } @@ -611,7 +611,7 @@ public class WallpaperGalleryController: ViewController { break } - strongSelf.apply?(entry, options, centralItemNode.cropRect) + strongSelf.apply?(entry, options, centralItemNode.cropRect, centralItemNode.brightness) } } } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index a63e7ff8870..ab3dc13b3df 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -95,23 +95,27 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let wrapperNode: ASDisplayNode let imageNode: TransformImageNode let nativeNode: WallpaperBackgroundNode + let brightnessNode: ASDisplayNode private let statusNode: RadialStatusNode private let blurredNode: BlurredImageNode let cropNode: WallpaperCropNode - private var cancelButtonNode: WallpaperNavigationButtonNode - private var shareButtonNode: WallpaperNavigationButtonNode + private let cancelButtonNode: WallpaperNavigationButtonNode + private let shareButtonNode: WallpaperNavigationButtonNode - private var blurButtonNode: WallpaperOptionButtonNode - private var motionButtonNode: WallpaperOptionButtonNode - private var patternButtonNode: WallpaperOptionButtonNode - private var colorsButtonNode: WallpaperOptionButtonNode - private var playButtonNode: WallpaperNavigationButtonNode + private let blurButtonNode: WallpaperOptionButtonNode + private let motionButtonNode: WallpaperOptionButtonNode + private let patternButtonNode: WallpaperOptionButtonNode + private let colorsButtonNode: WallpaperOptionButtonNode + private let playButtonNode: WallpaperNavigationButtonNode + private let sliderNode: WallpaperSliderNode private let messagesContainerNode: ASDisplayNode private var messageNodes: [ListViewItemNode]? private var validMessages: [String]? + private let serviceBackgroundNode: NavigationBackgroundNode + fileprivate let _ready = Promise() private let fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() @@ -149,6 +153,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.statusNode.isUserInteractionEnabled = false self.blurredNode = BlurredImageNode() + self.brightnessNode = ASDisplayNode() self.messagesContainerNode = ASDisplayNode() self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) @@ -160,11 +165,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.motionButtonNode.setEnabled(false) self.patternButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Pattern, value: .check(false)) self.patternButtonNode.setEnabled(false) + + self.serviceBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.35)) + + var sliderValueChangedImpl: ((CGFloat) -> Void)? + self.sliderNode = WallpaperSliderNode(minValue: 0.0, maxValue: 1.0, value: 0.5, valueChanged: { value, _ in + sliderValueChangedImpl?(value) + }) self.colorsButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_WallpaperColors, value: .colors(false, [.clear])) - self.cancelButtonNode = WallpaperNavigationButtonNode(content: .text(self.presentationData.strings.Common_Cancel)) - self.shareButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Chat/Links/Share"), size: CGSize(width: 28.0, height: 28.0))) + self.cancelButtonNode = WallpaperNavigationButtonNode(content: .text(self.presentationData.strings.Common_Cancel), dark: false) + self.shareButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Chat/Links/Share"), size: CGSize(width: 28.0, height: 28.0)), dark: false) self.playButtonPlayImage = generateImage(CGSize(width: 48.0, height: 48.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -193,7 +205,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.playButtonRotateImage = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorRotateIcon"), color: .white) - self.playButtonNode = WallpaperNavigationButtonNode(content: .icon(image: self.playButtonPlayImage, size: CGSize(width: 48.0, height: 48.0))) + self.playButtonNode = WallpaperNavigationButtonNode(content: .icon(image: self.playButtonPlayImage, size: CGSize(width: 48.0, height: 48.0)), dark: true) super.init() @@ -217,6 +229,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.addSubnode(self.wrapperNode) //self.addSubnode(self.statusNode) + self.addSubnode(self.serviceBackgroundNode) self.addSubnode(self.messagesContainerNode) self.addSubnode(self.blurButtonNode) @@ -224,9 +237,12 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.addSubnode(self.patternButtonNode) self.addSubnode(self.colorsButtonNode) self.addSubnode(self.playButtonNode) + self.addSubnode(self.sliderNode) self.addSubnode(self.cancelButtonNode) self.addSubnode(self.shareButtonNode) + self.imageNode.addSubnode(self.brightnessNode) + self.blurButtonNode.addTarget(self, action: #selector(self.toggleBlur), forControlEvents: .touchUpInside) self.motionButtonNode.addTarget(self, action: #selector(self.toggleMotion), forControlEvents: .touchUpInside) self.patternButtonNode.addTarget(self, action: #selector(self.togglePattern), forControlEvents: .touchUpInside) @@ -234,6 +250,24 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.playButtonNode.addTarget(self, action: #selector(self.togglePlay), forControlEvents: .touchUpInside) self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) self.shareButtonNode.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside) + + sliderValueChangedImpl = { [weak self] value in + if let self { + let value = (value - 0.5) * 2.0 + if value < 0.0 { + self.brightnessNode.backgroundColor = UIColor(rgb: 0x000000) + self.brightnessNode.layer.compositingFilter = nil + self.brightnessNode.alpha = value * -1.0 + } else if value > 0.0 { + self.brightnessNode.backgroundColor = UIColor(rgb: 0xffffff) + self.brightnessNode.layer.compositingFilter = "overlayBlendMode" + self.brightnessNode.alpha = value + } else { + self.brightnessNode.layer.compositingFilter = nil + self.brightnessNode.alpha = 0.0 + } + } + } } deinit { @@ -255,6 +289,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } + var brightness: CGFloat? { + guard let entry = self.entry else { + return nil + } + switch entry { + case .asset, .contextResult: + return (self.sliderNode.value - 0.5) * 2.0 + default: + return nil + } + } + override func ready() -> Signal { return self._ready.get() } @@ -721,7 +767,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } func setBlurEnabled(_ enabled: Bool, animated: Bool) { - let blurRadius: CGFloat = 45.0 + let blurRadius: CGFloat = 30.0 var animated = animated if animated, let (layout, _) = self.validLayout { @@ -732,13 +778,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { if enabled { if self.blurredNode.supernode == nil { - if self.cropNode.supernode != nil { - self.blurredNode.frame = self.imageNode.bounds - self.imageNode.addSubnode(self.blurredNode) - } else { - self.blurredNode.frame = self.imageNode.bounds - self.imageNode.addSubnode(self.blurredNode) - } + self.blurredNode.frame = self.imageNode.bounds + self.imageNode.insertSubnode(self.blurredNode, at: 0) } if animated { @@ -914,12 +955,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let buttonSize = CGSize(width: maxButtonWidth, height: 30.0) let alpha = 1.0 - min(1.0, max(0.0, abs(offset.y) / 50.0)) - let additionalYOffset: CGFloat = 0.0 - /*if self.patternButtonNode.isSelected { - additionalYOffset = -235.0 - } else if self.colorsButtonNode.isSelected { - additionalYOffset = -235.0 - }*/ + var additionalYOffset: CGFloat = 0.0 + if let source = self.source { + switch source { + case .asset, .contextResult: + additionalYOffset -= 44.0 + default: + break + } + } let buttonSpacing: CGFloat = 18.0 @@ -937,13 +981,17 @@ final class WallpaperGalleryItemNode: GalleryItemNode { var motionFrame = centerButtonFrame var motionAlpha: CGFloat = 0.0 - + var colorsFrame = CGRect(origin: CGPoint(x: rightButtonFrame.maxX - colorsButtonSize.width, y: rightButtonFrame.minY), size: colorsButtonSize) var colorsAlpha: CGFloat = 0.0 let playFrame = CGRect(origin: CGPoint(x: centerButtonFrame.midX - playButtonSize.width / 2.0, y: centerButtonFrame.midY - playButtonSize.height / 2.0), size: playButtonSize) var playAlpha: CGFloat = 0.0 + let sliderSize = CGSize(width: 268.0, height: 30.0) + let sliderFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - sliderSize.width) / 2.0) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 52.0 + offset.y), size: sliderSize) + var sliderIsHidden = true + let cancelSize = self.cancelButtonNode.measure(layout.size) let cancelFrame = CGRect(origin: CGPoint(x: 16.0 + offset.x, y: 16.0), size: cancelSize) @@ -958,11 +1006,13 @@ final class WallpaperGalleryItemNode: GalleryItemNode { blurFrame = leftButtonFrame motionAlpha = 1.0 motionFrame = rightButtonFrame + sliderIsHidden = false case .contextResult: blurAlpha = 1.0 blurFrame = leftButtonFrame motionAlpha = 1.0 motionFrame = rightButtonFrame + sliderIsHidden = false case let .wallpaper(wallpaper, _): switch wallpaper { case .builtin: @@ -1047,17 +1097,17 @@ final class WallpaperGalleryItemNode: GalleryItemNode { transition.updateAlpha(node: self.playButtonNode, alpha: playAlpha * alpha) transition.updateSublayerTransformScale(node: self.playButtonNode, scale: max(0.1, playAlpha)) + transition.updateFrame(node: self.sliderNode, frame: sliderFrame) + self.sliderNode.updateLayout(size: sliderFrame.size) + self.sliderNode.isHidden = sliderIsHidden + transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame) transition.updateFrame(node: self.shareButtonNode, frame: shareFrame) } private func updateMessagesLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) { - let bottomInset: CGFloat = 132.0 + var bottomInset: CGFloat = 132.0 - if self.patternButtonNode.isSelected || self.colorsButtonNode.isSelected { - //bottomInset = 350.0 - } - var items: [ListViewItem] = [] let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) let otherPeerId = self.context.account.peerId @@ -1125,6 +1175,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { case .asset, .contextResult: topMessageText = presentationData.strings.WallpaperPreview_CropTopText bottomMessageText = presentationData.strings.WallpaperPreview_CropBottomText + bottomInset += 44.0 case .customColor: topMessageText = presentationData.strings.WallpaperPreview_CustomColorTopText bottomMessageText = presentationData.strings.WallpaperPreview_CustomColorBottomText @@ -1188,6 +1239,14 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.messageNodes = messageNodes } + if let _ = serviceMessageText, let messageNodes = self.messageNodes, let node = messageNodes.last { + if let backgroundNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.first { + let serviceBackgroundFrame = backgroundNode.view.convert(backgroundNode.bounds, to: self.view).offsetBy(dx: 0.0, dy: -1.0).insetBy(dx: 0.0, dy: -1.0) + transition.updateFrame(node: self.serviceBackgroundNode, frame: serviceBackgroundFrame) + self.serviceBackgroundNode.update(size: serviceBackgroundFrame.size, cornerRadius: serviceBackgroundFrame.height / 2.0, transition: transition) + } + } + let alpha = 1.0 - min(1.0, max(0.0, abs(offset.y) / 50.0)) if let messageNodes = self.messageNodes { @@ -1237,6 +1296,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } self.blurredNode.frame = self.imageNode.bounds } + self.brightnessNode.frame = self.imageNode.bounds let additionalYOffset: CGFloat = 0.0 diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift index 13c47595c02..7c595e0f15c 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift @@ -17,39 +17,6 @@ enum WallpaperGalleryToolbarDoneButtonType { case none } -final class WallpaperLightButtonBackgroundNode: ASDisplayNode { - private let backgroundNode: NavigationBackgroundNode - private let overlayNode: ASDisplayNode - private let lightNode: ASDisplayNode - - override init() { - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.01), enableBlur: true) - self.overlayNode = ASDisplayNode() - self.overlayNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.55) - self.overlayNode.layer.compositingFilter = "overlayBlendMode" - - self.lightNode = ASDisplayNode() - self.lightNode.backgroundColor = UIColor(rgb: 0xf2f2f2, alpha: 0.3) - - super.init() - - self.clipsToBounds = true - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.overlayNode) - //self.addSubnode(self.lightNode) - } - - func updateLayout(size: CGSize) { - let frame = CGRect(origin: .zero, size: size) - self.backgroundNode.frame = frame - self.overlayNode.frame = frame - self.lightNode.frame = frame - - self.backgroundNode.update(size: size, transition: .immediate) - } -} - final class WallpaperGalleryToolbarNode: ASDisplayNode { private var theme: PresentationTheme private let strings: PresentationStrings diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 5ccd6243ded..ecec3746922 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -34,6 +34,61 @@ private func generateColorsImage(diameter: CGFloat, colors: [UIColor]) -> UIImag }) } +final class WallpaperLightButtonBackgroundNode: ASDisplayNode { + private let backgroundNode: NavigationBackgroundNode + private let overlayNode: ASDisplayNode + private let lightNode: ASDisplayNode + + override init() { + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.3), enableBlur: true, enableSaturation: false) + self.overlayNode = ASDisplayNode() + self.overlayNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.75) + self.overlayNode.layer.compositingFilter = "overlayBlendMode" + + self.lightNode = ASDisplayNode() + self.lightNode.backgroundColor = UIColor(rgb: 0xf2f2f2, alpha: 0.2) + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.overlayNode) + self.addSubnode(self.lightNode) + } + + func updateLayout(size: CGSize) { + let frame = CGRect(origin: .zero, size: size) + self.backgroundNode.frame = frame + self.overlayNode.frame = frame + self.lightNode.frame = frame + + self.backgroundNode.update(size: size, transition: .immediate) + } +} + +final class WallpaperOptionBackgroundNode: ASDisplayNode { + private let backgroundNode: NavigationBackgroundNode + + override init() { + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.3), enableBlur: true, enableSaturation: false) + + super.init() + + self.clipsToBounds = true + self.isUserInteractionEnabled = false + + self.addSubnode(self.backgroundNode) + } + + func updateLayout(size: CGSize) { + let frame = CGRect(origin: .zero, size: size) + self.backgroundNode.frame = frame + + self.backgroundNode.update(size: size, transition: .immediate) + } +} + final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { enum Content { case icon(image: UIImage?, size: CGSize) @@ -42,7 +97,7 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { private let content: Content - private let backgroundNode: WallpaperLightButtonBackgroundNode + private let backgroundNode: ASDisplayNode private let iconNode: ASImageNode @@ -52,10 +107,14 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { self.iconNode.image = generateTintedImage(image: image, color: .white) } - init(content: Content) { + init(content: Content, dark: Bool) { self.content = content - self.backgroundNode = WallpaperLightButtonBackgroundNode() + if dark { + self.backgroundNode = WallpaperOptionBackgroundNode() + } else { + self.backgroundNode = WallpaperLightButtonBackgroundNode() + } self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false @@ -94,11 +153,6 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { didSet { -// if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) { -// self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate) -// } else { -// self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate) -// } } } @@ -119,7 +173,11 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { let size = self.bounds.size self.backgroundNode.frame = self.bounds - self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) + if let backgroundNode = self.backgroundNode as? WallpaperOptionBackgroundNode { + backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) + } else if let backgroundNode = self.backgroundNode as? WallpaperLightButtonBackgroundNode { + backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) + } self.backgroundNode.cornerRadius = size.height / 2.0 self.iconNode.frame = self.bounds @@ -132,7 +190,7 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { - private let backgroundNode: NavigationBackgroundNode + private let backgroundNode: WallpaperOptionBackgroundNode private let checkNode: CheckNode private let colorNode: ASImageNode @@ -172,9 +230,8 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { self._value = value self.title = title - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.01)) - self.backgroundNode.cornerRadius = 14.0 - + self.backgroundNode = WallpaperOptionBackgroundNode() + self.checkNode = CheckNode(theme: CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: false, borderWidth: 1.5)) self.checkNode.isUserInteractionEnabled = false @@ -186,6 +243,9 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { super.init() + self.clipsToBounds = true + self.cornerRadius = 14.0 + switch value { case let .check(selected): self.checkNode.isHidden = false @@ -202,6 +262,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { } self.addSubnode(self.backgroundNode) + self.addSubnode(self.checkNode) self.addSubnode(self.textNode) self.addSubnode(self.colorNode) @@ -227,11 +288,6 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { didSet { -// if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) { -// self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate) -// } else { -// self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate) -// } } } @@ -322,7 +378,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { super.layout() self.backgroundNode.frame = self.bounds - self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: 15.0, transition: .immediate) + self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) guard let _ = self.textSize else { return @@ -340,3 +396,147 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { } } } + +final class WallpaperSliderNode: ASDisplayNode { + let minValue: CGFloat + let maxValue: CGFloat + var value: CGFloat = 1.0 { + didSet { + if let size = self.validLayout { + self.updateLayout(size: size) + } + } + } + + private let backgroundNode: NavigationBackgroundNode + + private let foregroundNode: ASDisplayNode + private let foregroundLightNode: ASDisplayNode + private let leftIconNode: ASImageNode + private let rightIconNode: ASImageNode + + private let valueChanged: (CGFloat, Bool) -> Void + + private let hapticFeedback = HapticFeedback() + + private var validLayout: CGSize? + + init(minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) { + self.minValue = minValue + self.maxValue = maxValue + self.value = value + self.valueChanged = valueChanged + + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.3), enableBlur: true, enableSaturation: false) + + self.foregroundNode = ASDisplayNode() + self.foregroundNode.clipsToBounds = true + self.foregroundNode.cornerRadius = 3.0 + self.foregroundNode.isAccessibilityElement = false + self.foregroundNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.75) + self.foregroundNode.layer.compositingFilter = "overlayBlendMode" + self.foregroundNode.isUserInteractionEnabled = false + + self.foregroundLightNode = ASDisplayNode() + self.foregroundLightNode.clipsToBounds = true + self.foregroundLightNode.cornerRadius = 3.0 + self.foregroundLightNode.backgroundColor = UIColor(rgb: 0xf2f2f2, alpha: 0.2) + + self.leftIconNode = ASImageNode() + self.leftIconNode.displaysAsynchronously = false + self.leftIconNode.image = UIImage(bundleImageName: "Settings/WallpaperBrightnessMin") + self.leftIconNode.contentMode = .center + + self.rightIconNode = ASImageNode() + self.rightIconNode.displaysAsynchronously = false + self.rightIconNode.image = UIImage(bundleImageName: "Settings/WallpaperBrightnessMax") + self.rightIconNode.contentMode = .center + + super.init() + + self.clipsToBounds = true + self.cornerRadius = 15.0 + self.isUserInteractionEnabled = true + + self.addSubnode(self.backgroundNode) + + self.addSubnode(self.foregroundNode) + self.addSubnode(self.foregroundLightNode) + + self.addSubnode(self.leftIconNode) + self.addSubnode(self.rightIconNode) + } + + override func didLoad() { + super.didLoad() + + let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + self.view.addGestureRecognizer(panGestureRecognizer) + + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + self.view.addGestureRecognizer(tapGestureRecognizer) + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + self.validLayout = size + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: size)) + self.backgroundNode.update(size: size, transition: transition) + + if let icon = self.leftIconNode.image { + transition.updateFrame(node: self.leftIconNode, frame: CGRect(origin: CGPoint(x: 7.0, y: floorToScreenPixels((size.height - icon.size.height) / 2.0)), size: icon.size)) + } + + if let icon = self.rightIconNode.image { + transition.updateFrame(node: self.rightIconNode, frame: CGRect(origin: CGPoint(x: size.width - icon.size.width - 6.0, y: floorToScreenPixels((size.height - icon.size.height) / 2.0)), size: icon.size)) + } + + let range = self.maxValue - self.minValue + let value = (self.value - self.minValue) / range + let foregroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: value * size.width, height: size.height)) + transition.updateFrameAdditive(node: self.foregroundNode, frame: foregroundFrame) + transition.updateFrameAdditive(node: self.foregroundLightNode, frame: foregroundFrame) + } + + @objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) { + let range = self.maxValue - self.minValue + switch gestureRecognizer.state { + case .began: + break + case .changed: + let previousValue = self.value + + let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x + let delta = translation / self.bounds.width * range + self.value = max(self.minValue, min(self.maxValue, self.value + delta)) + gestureRecognizer.setTranslation(CGPoint(), in: gestureRecognizer.view) + + if self.value == 2.0 && previousValue != 2.0 { + self.hapticFeedback.impact(.soft) + } else if self.value == 1.0 && previousValue != 1.0 { + self.hapticFeedback.impact(.soft) + } else if self.value == 2.5 && previousValue != 2.5 { + self.hapticFeedback.impact(.soft) + } else if self.value == 0.05 && previousValue != 0.05 { + self.hapticFeedback.impact(.soft) + } + if abs(previousValue - self.value) >= 0.001 { + self.valueChanged(self.value, false) + } + case .ended: + let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x + let delta = translation / self.bounds.width * range + self.value = max(self.minValue, min(self.maxValue, self.value + delta)) + self.valueChanged(self.value, true) + default: + break + } + } + + @objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) { + let range = self.maxValue - self.minValue + let location = gestureRecognizer.location(in: gestureRecognizer.view) + self.value = max(self.minValue, min(self.maxValue, self.minValue + location.x / self.bounds.width * range)) + self.valueChanged(self.value, true) + } +} diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index 3458593c55c..002043cccfa 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -218,16 +218,18 @@ public final class InitialPresentationDataAndSettings { public let callListSettings: CallListSettings public let inAppNotificationSettings: InAppNotificationSettings public let mediaInputSettings: MediaInputSettings + public let mediaDisplaySettings: MediaDisplaySettings public let stickerSettings: StickerSettings public let experimentalUISettings: ExperimentalUISettings - public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) { + public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) { self.presentationData = presentationData self.automaticMediaDownloadSettings = automaticMediaDownloadSettings self.autodownloadSettings = autodownloadSettings self.callListSettings = callListSettings self.inAppNotificationSettings = inAppNotificationSettings self.mediaInputSettings = mediaInputSettings + self.mediaDisplaySettings = mediaDisplaySettings self.stickerSettings = stickerSettings self.experimentalUISettings = experimentalUISettings } @@ -242,6 +244,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManagerY}F5WVwP@M0i2gxc?20YQMoZi=EU>MFejJt*sqqe9ZQl2WAo^?AdYeWaZ1 z!Jr<=`JTt|&Hdfm7iH+3D}CYT4}UrDU%vFOUb*S`AU|D|&q@w zecjZ9KU{%bGYB|@p&Cj(x9*B{gqpU#hDJApVrXR-^6Xq6dKC=Fq~(xl9bE#O(AA>N z@2|kBYsJ!}q7G}dUZ5~B@7pAxs6pFqcqCxw%5o)j;$a^t{^LdiOi@`jzM>Ry9rHn zUG`xZJ_N07LSM!l0qr$v83x~NH{Hfc+$lUPYCVnt9IC_tJGMMABFVmnw35sV#X z_7Oyj(3C|*1|?H6a4{Jj`NUh)muaQ1FopIJlxjhiv~+390hn?&GZpHN%{^JurBi?lgWN~BgHjmW`B zzA-Q$EezosC3;+qQBkHgo(EMWdRB4=p^?SyD%7itXv)*5NU(%iB1z$>KCK5}ReaSv zB*ym^#At}9l_v{`9mhgFl#-xvmA)bnpp8bigo?#X5wLhgx@Y9}U@&8Q1Xv!70z793 zvY_S6j>(dRO?_x_yzAwy;**mhBsr@OvHfWrBs!=y9hQ*X7=e6NLXB#4ICn@L$LNsc zh-8BjF?5O6DJ5ly>C2`hc40Oou~#`GW&La!B9cu=#ONURep)UBR)qA&hZ(_4;_6FK7S$PmIs_R)75W_vBai+sDa(U#ExN_Rajo zzfVIj?N0nID=pzg(q=l(pHJg-_A;PvOx*2!yi7-El<9l{@3$vlzBMSD;RWJszxsLi z08_nqu4VSycY|u5Ihq z1GcX$d|de>p&E9|N2nUmKu@6_X}UU97Y%H^hRP~kyqvbX{d97Yy`Sz7j%VljFg^Fr pb`Q@rD67ppU%WUR1H5{-`+tJ<@t>aU=@Q+kF?P9|n;(9A`xVuKu`d7s literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json new file mode 100644 index 00000000000..68cb6916eb3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "brightness_min.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/brightness_min.pdf b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/brightness_min.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ec3ccfc3aff7233059dfd5f1ac15e869f8cfaf6b GIT binary patch literal 4376 zcmbuDU2oeq6o&8hD|k~Ny@*VT9}`tthlhD}v${T4d6{KONU?kPGZW(CLM(o6`^Vu)6o9wl@-jON`?lU@ zDp%GNz66%JtV$ybaV~SE%R-hy<<-9 z;@5^>C+1ne)V#&=-RfQ64PtS#-!*sbAU0jU-nWO{%f4yFL;KkEnJUD(YliS^WX-mI z&S>)8*Tc|m-}ReaMrv*`0k(e?XV=~1wnnU8BR*pBVZUzoUH^E>?_QptKeyYqAB4;< zFU7juKE+3#WC7=qC@GN9{Qj?(k)ne@&7Pu7k? zKCPYBj#tMqrLf(-JPhR9f;`j|F~n>MJ84x8V}32ApoAM2Z+ic>hldnXk}!s5F{4Ss zIBfn}th&txHup_?5Fa6F0g0bZ2O@DN;lFq5;preK$8H0<-u1)P)$R+;&gJ;|-b0YR zplR>xVc*e2B>cA~e&^LJYI2eU29xf0zx5rhv!HBeL%aQ9py(l|CU1VOPrz-+@G-Wd z)A*V<7H!sP#VE3*ZVd6Y60~Zx0<^*B(bduDPzHP$IG2INs{Olb+S?zl#PTjnel>^e zFBFN>sGvjy#P3jaoYj@|2%>hjEVO8-zMPI@a%lQY=GqixiDk^4FLb5FHhaoEPl8NU zl%BB9jjKwFG-K%V$~v!&PzBBKWhqi*oG*(~G?*I4d0m-t+$>G!x^T`(Or1?-)MQm| zwJJy}!zx!sqb`!xAT33#HHE>tC@t|}*2<_s2uyBdSt<*w@Ld%W5iogG>9X>X*}QPZ znMx>>Y-yx4B1Kq)(i#LHLPXyvi*YOr;lu&pstPFRjHt;p`RE;v03N4vYh_s}LP1(Y zGH0zR4A!wUc8irVA@>n&>x-f~@?RMoM@s4h0vY2Ag{0#M95FT~p=@;&7@donBWuVW zRcH&gfg-P+(poT#N)TjYP|PVo zgM~Ktl|*JD7b2{3S!iEi97|)xtdvQUx!gNlIof0)Cy`gB^4PvCibagk-VE5ow{7AvTTEg)R(G2xLU5|2&SDB}!v|flhrr-v8J}QvpT@c>gm5 z3j1H?UU@@j0VAA4Ri!E_fGMn;q1Z-gNI64GL^VEC9Y*QaXBfAT%4cc3)l#htqjg+J zq7ST<5ru#YBh%yrV8)f5H84pGNm#I#%Rf8C`j{lepINNwv*!una5H18xbU;(td9wK z&2<3nMCc-tqd!7E($R_F+_#!e#c;|4A!F&FOpbo)STw2?9UkH6r{<$lhDbPrnIs24 zGd|Cws1&iv6b94y+)o_{IwKnABleqsedwUP-sI@$~L{L9DwWNiUmU~ZyTV>L!zUlkjfbRadU%shv zGn)F~_wBl?ztzHH!-gI)KFyM1zf8^X+-NV+8slbk5Yz+D@TR8Q63)E9l2&9bcF{+mjh;SK!^WosY4}c`1fdN$LDnG}$D=HI@e}0nhk#>*UgmV7 zE^&??kHX&)PA6&)i%v#)oZvGW_I3BU#WfhAd$+)C_O&?c*2qMP{o;1F!!l5m e$CX{&b^o*!F|5I1sQ1GdLnC#Wy?XWb!}Y&j@V6=e literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f0772f42296..a71255f408b 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -845,7 +845,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.chatDisplayNode.dismissInput() let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true)) - wallpaperPreviewController.apply = { wallpaper, options, _ in + wallpaperPreviewController.apply = { wallpaper, options, _, _ in let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, wallpaper: nil) |> deliverOnMainQueue).start(completed: { [weak wallpaperPreviewController] in wallpaperPreviewController?.dismiss() @@ -4264,12 +4264,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if value { openWebView() } else { - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.WebApp_OpenWebViewAlertTitle, text: strongSelf.presentationData.strings.WebApp_OpenWebViewAlertText(botName).string, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { - if let strongSelf = self { - let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start() - openWebView() - } - })], parseMarkdown: true), in: .window(.root), with: nil) + let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), commit: { + let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start() + openWebView() + }, showMore: nil) + strongSelf.present(controller, in: .window(.root)) } }) }, activateAdAction: { [weak self] messageId in @@ -17173,7 +17172,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } - private func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?, forceExternal: Bool = false) { + private func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?, forceExternal: Bool = false, concealed: Bool = false) { guard let peerId = self.chatLocation.peerId else { return } @@ -17230,7 +17229,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), attachBotStart: attachBotStart)) } case let .withBotApp(botAppStart): - strongSelf.presentBotApp(botApp: botAppStart.botApp, payload: botAppStart.payload) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self, let peer { + let openBotApp = { [weak self] in + if let strongSelf = self { + strongSelf.presentBotApp(botApp: botAppStart.botApp, payload: botAppStart.payload) + } + } + if concealed { + let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, commit: { + openBotApp() + }, showMore: { [weak self] in + if let strongSelf = self { + strongSelf.openResolved(result: .peer(peer._asPeer(), .info), sourceMessageId: nil) + } + }) + strongSelf.present(controller, in: .window(.root)) + } else { + openBotApp() + } + } + }) default: break } @@ -17256,7 +17276,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in self?.present(c, in: .window(.root)) }, openResolved: { [weak self] resolved in - self?.openResolved(result: resolved, sourceMessageId: message?.id, forceExternal: forceExternal) + self?.openResolved(result: resolved, sourceMessageId: message?.id, forceExternal: forceExternal, concealed: concealed) }) }, performAction: true) } @@ -18535,9 +18555,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) controller.navigationPresentation = .modal - controller.apply = { [weak self] wallpaper, options, cropRect in + controller.apply = { [weak self] wallpaper, options, cropRect, brightness in if let strongSelf = self { - uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightnessMultiplier: nil, peerId: peerId, completion: { + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, peerId: peerId, completion: { dismissControllers() }) } diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index be5e680b972..2ee3fccb9a7 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -398,7 +398,7 @@ private func fetchCachedBlurredWallpaperRepresentation(resource: MediaResource, let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))" let url = URL(fileURLWithPath: path) - if let colorImage = blurredImage(image, radius: 20.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + if let colorImage = blurredImage(image, radius: 30.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) let colorQuality: Float = 0.5 @@ -447,7 +447,7 @@ private func fetchCachedBlurredWallpaperRepresentation(account: Account, resourc let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))" let url = URL(fileURLWithPath: path) - if let colorImage = blurredImage(image, radius: 20.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + if let colorImage = blurredImage(image, radius: 30.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) let colorQuality: Float = 0.5 diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 2d8bcb82c54..43d8e09c8ee 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -162,6 +162,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { public let currentMediaInputSettings: Atomic private var mediaInputSettingsDisposable: Disposable? + public let currentMediaDisplaySettings: Atomic + private var mediaDisplaySettingsDisposable: Disposable? + public let currentStickerSettings: Atomic private var stickerSettingsDisposable: Disposable? @@ -241,6 +244,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.currentAutomaticMediaDownloadSettings = initialPresentationDataAndSettings.automaticMediaDownloadSettings self.currentAutodownloadSettings = Atomic(value: initialPresentationDataAndSettings.autodownloadSettings) self.currentMediaInputSettings = Atomic(value: initialPresentationDataAndSettings.mediaInputSettings) + self.currentMediaDisplaySettings = Atomic(value: initialPresentationDataAndSettings.mediaDisplaySettings) self.currentStickerSettings = Atomic(value: initialPresentationDataAndSettings.stickerSettings) self.currentInAppNotificationSettings = Atomic(value: initialPresentationDataAndSettings.inAppNotificationSettings) @@ -359,6 +363,15 @@ public final class SharedAccountContextImpl: SharedAccountContext { } }) + self.mediaDisplaySettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.mediaDisplaySettings]) + |> deliverOnMainQueue).start(next: { [weak self] sharedData in + if let strongSelf = self { + if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) { + let _ = strongSelf.currentMediaDisplaySettings.swap(settings) + } + } + }) + self.stickerSettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue).start(next: { [weak self] sharedData in if let strongSelf = self { @@ -895,6 +908,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.currentAutodownloadSettingsDisposable.dispose() self.inAppNotificationSettingsDisposable?.dispose() self.mediaInputSettingsDisposable?.dispose() + self.mediaDisplaySettingsDisposable?.dispose() self.callDisposable?.dispose() self.groupCallDisposable?.dispose() self.callStateDisposable?.dispose() diff --git a/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift b/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift new file mode 100644 index 00000000000..2e59eab7878 --- /dev/null +++ b/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift @@ -0,0 +1,50 @@ +import Foundation +import Postbox +import TelegramCore +import SwiftSignalKit + +public struct MediaDisplaySettings: Codable, Equatable { + public let showNextMediaOnTap: Bool + + public static var defaultSettings: MediaDisplaySettings { + return MediaDisplaySettings(showNextMediaOnTap: true) + } + + public init(showNextMediaOnTap: Bool) { + self.showNextMediaOnTap = showNextMediaOnTap + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.showNextMediaOnTap = (try container.decode(Int32.self, forKey: "showNextMediaOnTap")) != 0 + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode((self.showNextMediaOnTap ? 1 : 0) as Int32, forKey: "showNextMediaOnTap") + } + + public static func ==(lhs: MediaDisplaySettings, rhs: MediaDisplaySettings) -> Bool { + return lhs.showNextMediaOnTap == rhs.showNextMediaOnTap + } + + public func withUpdatedShowNextMediaOnTap(_ showNextMediaOnTap: Bool) -> MediaDisplaySettings { + return MediaDisplaySettings(showNextMediaOnTap: showNextMediaOnTap) + } +} + +public func updateMediaDisplaySettingsInteractively(accountManager: AccountManager, _ f: @escaping (MediaDisplaySettings) -> MediaDisplaySettings) -> Signal { + return accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.mediaDisplaySettings, { entry in + let currentSettings: MediaDisplaySettings + if let entry = entry?.get(MediaDisplaySettings.self) { + currentSettings = entry + } else { + currentSettings = MediaDisplaySettings.defaultSettings + } + return PreferencesEntry(f(currentSettings)) + }) + } +} diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift index f53c2960374..5ac78a35606 100644 --- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift +++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift @@ -39,6 +39,7 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 { case intentsSettings = 17 case translationSettings = 18 case drawingSettings = 19 + case mediaDisplaySettings = 20 } public struct ApplicationSpecificSharedDataKeys { @@ -62,6 +63,7 @@ public struct ApplicationSpecificSharedDataKeys { public static let intentsSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.intentsSettings.rawValue) public static let translationSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.translationSettings.rawValue) public static let drawingSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.drawingSettings.rawValue) + public static let mediaDisplaySettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.mediaDisplaySettings.rawValue) } private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 { diff --git a/submodules/WebUI/Sources/WebAppOpenConfirmationController.swift b/submodules/WebUI/Sources/WebAppOpenConfirmationController.swift new file mode 100644 index 00000000000..2e379c1d3ef --- /dev/null +++ b/submodules/WebUI/Sources/WebAppOpenConfirmationController.swift @@ -0,0 +1,287 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import AppBundle +import AvatarNode + +private final class WebAppLaunchConfirmationAlertContentNode: AlertContentNode { + private let strings: PresentationStrings + private let title: String + private let text: String + private let showMore: Bool + + private let titleNode: ImmediateTextNode + private let textNode: ASTextNode + private let avatarNode: AvatarNode + + private let moreButton: HighlightableButtonNode + private let arrowNode: ASImageNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private var validLayout: CGSize? + + private let morePressed: () -> Void + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer, title: String, text: String, showMore: Bool, actions: [TextAlertAction], morePressed: @escaping () -> Void) { + self.strings = strings + self.title = title + self.text = text + self.showMore = showMore + self.morePressed = morePressed + + self.titleNode = ImmediateTextNode() + self.titleNode.displaysAsynchronously = false + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.textAlignment = .center + + self.textNode = ASTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.maximumNumberOfLines = 0 + + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) + + self.moreButton = HighlightableButtonNode() + + self.arrowNode = ASImageNode() + self.arrowNode.displaysAsynchronously = false + self.arrowNode.displayWithoutProcessing = true + self.arrowNode.isHidden = !showMore + self.arrowNode.contentMode = .scaleAspectFit + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + self.addSubnode(self.avatarNode) + self.addSubnode(self.moreButton) + self.moreButton.addSubnode(self.arrowNode) + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.updateTheme(theme) + + self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer) + + self.moreButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) + } + + @objc private func moreButtonPressed() { + self.morePressed() + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) + + self.moreButton.setAttributedTitle(NSAttributedString(string: self.strings.WebApp_LaunchMoreInfo, font: Font.regular(13.0), textColor: theme.accentColor), for: .normal) + self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.accentColor) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width, 270.0) + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) + + let avatarSize = CGSize(width: 60.0, height: 60.0) + self.avatarNode.updateSize(size: avatarSize) + + let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0), y: origin.y), size: avatarSize) + transition.updateFrame(node: self.avatarNode, frame: avatarFrame) + + origin.y += avatarSize.height + 17.0 + + if let arrowImage = self.arrowNode.image { + let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) + transition.updateFrame(node: self.arrowNode, frame: arrowFrame) + } + + let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) + origin.y += titleSize.height + 6.0 + + if self.showMore { + let moreButtonSize = self.moreButton.measure(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.moreButton, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - moreButtonSize.width) / 2.0) - 5.0, y: origin.y), size: moreButtonSize)) + transition.updateFrame(node: self.arrowNode, frame: CGRect(origin: CGPoint(x: moreButtonSize.width + 3.0, y: 4.0), size: CGSize(width: 9.0, height: 9.0))) + origin.y += moreButtonSize.height + 22.0 + } + + let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.horizontal + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) + + let contentWidth = max(size.width, minActionsWidth) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + var resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 25.0 + insets.top + insets.bottom) + if self.showMore { + resultSize.height += 37.0 + } + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + return resultSize + } +} + +public func webAppLaunchConfirmationController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: EnginePeer, commit: @escaping () -> Void, showMore: (() -> Void)?) -> AlertController { + let theme = defaultDarkColorPresentationTheme + let presentationData: PresentationData + if let updatedPresentationData { + presentationData = updatedPresentationData.initial + } else { + presentationData = context.sharedContext.currentPresentationData.with { $0 } + } + let strings = presentationData.strings + + var dismissImpl: ((Bool) -> Void)? + var contentNode: WebAppLaunchConfirmationAlertContentNode? + let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + dismissImpl?(true) + commit() + })] + + let title = peer.compactDisplayTitle + let text = presentationData.strings.WebApp_LaunchConfirmation + + contentNode = WebAppLaunchConfirmationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peer: peer, title: title, text: text, showMore: showMore != nil, actions: actions, morePressed: { + dismissImpl?(true) + showMore?() + }) + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) + dismissImpl = { [weak controller] animated in + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +} From 0a305bc84826760dd46c8dc34163ce934b66a4d2 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 7 Apr 2023 18:33:30 +0400 Subject: [PATCH 13/57] Add network stats --- .../PublicHeaders/MtProtoKit/MTContext.h | 2 +- .../MtProtoKit/MTMessageService.h | 2 +- .../PublicHeaders/MtProtoKit/MTRequest.h | 12 +- .../MtProtoKit/MTRequestContext.h | 1 + .../PublicHeaders/MtProtoKit/MTTransport.h | 4 +- .../MtProtoKit/Sources/GCDAsyncSocket.h | 2 +- .../MtProtoKit/Sources/GCDAsyncSocket.m | 5 +- .../Sources/MTBackupAddressSignals.m | 2 +- .../Sources/MTBindKeyMessageService.m | 2 +- .../Sources/MTDatacenterAuthMessageService.m | 2 +- .../Sources/MTDatacenterTransferAuthAction.m | 4 +- .../MTDiscoverDatacenterAddressAction.m | 2 +- submodules/MtProtoKit/Sources/MTProto.m | 10 +- submodules/MtProtoKit/Sources/MTRequest.m | 14 ++ .../Sources/MTRequestMessageService.m | 20 ++- .../Sources/MTResendMessageService.m | 2 +- .../MtProtoKit/Sources/MTTcpConnection.h | 2 +- .../MtProtoKit/Sources/MTTcpConnection.m | 27 ++-- .../MtProtoKit/Sources/MTTcpTransport.m | 6 +- .../Sources/MTTimeSyncMessageService.m | 2 +- submodules/MtProtoKit/Sources/MTTransport.m | 6 +- .../Sources/Account/Account.swift | 4 + .../Sources/Network/Download.swift | 21 +-- .../Sources/Network/MultipartFetch.swift | 108 +++++++++++----- .../Network/MultiplexedRequestManager.swift | 27 ++-- ...tworkFrameworkTcpConnectionInterface.swift | 3 +- .../Sources/Network/NetworkStatsContext.swift | 122 ++++++++++++++++++ .../TelegramCore/Sources/State/Fetch.swift | 2 +- .../State/FetchSecretFileResource.swift | 2 +- .../Resources/TelegramEngineResources.swift | 2 +- 30 files changed, 323 insertions(+), 97 deletions(-) create mode 100644 submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h index 03c3202c86b..fa26508ecba 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h @@ -21,7 +21,7 @@ @protocol MTTcpConnectionInterfaceDelegate - (void)connectionInterfaceDidReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; -- (void)connectionInterfaceDidReadData:(NSData * _Nonnull)rawData withTag:(long)tag; +- (void)connectionInterfaceDidReadData:(NSData * _Nonnull)rawData withTag:(long)tag networkType:(int32_t)networkType; - (void)connectionInterfaceDidConnect; - (void)connectionInterfaceDidDisconnectWithError:(NSError * _Nullable)error; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h index 7f3d013a62c..1dd9fdd4f80 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h @@ -21,7 +21,7 @@ - (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo scheme:(MTTransportScheme *)scheme; - (void)mtProtoDidChangeSession:(MTProto *)mtProto; - (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(int64_t)firstValidMessageId otherValidMessageIds:(NSArray *)otherValidMessageIds; -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector; +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType; - (void)mtProto:(MTProto *)mtProto receivedQuickAck:(int32_t)quickAckId; - (void)mtProto:(MTProto *)mtProto transactionsMayHaveFailed:(NSArray *)transactionIds; - (void)mtProtoAllTransactionsMayHaveFailed:(MTProto *)mtProto; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequest.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequest.h index 5f5d3d1248e..28dba10b2d4 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequest.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequest.h @@ -6,6 +6,16 @@ @class MTRequestErrorContext; @class MTRpcError; +@interface MTRequestResponseInfo : NSObject + +@property (nonatomic, readonly) int32_t networkType; +@property (nonatomic, readonly) double timestamp; +@property (nonatomic, readonly) double duration; + +- (instancetype)initWithNetworkType:(int32_t)networkType timestamp:(double)timestamp duration:(double)duration; + +@end + @interface MTRequest : NSObject @property (nonatomic, strong, readonly) id internalId; @@ -24,7 +34,7 @@ @property (nonatomic) bool passthroughPasswordEntryError; @property (nonatomic) bool needsTimeoutTimer; -@property (nonatomic, copy) void (^completed)(id result, NSTimeInterval completionTimestamp, MTRpcError *error); +@property (nonatomic, copy) void (^completed)(id result, MTRequestResponseInfo *info, MTRpcError *error); @property (nonatomic, copy) void (^progressUpdated)(float progress, NSUInteger packetLength); @property (nonatomic, copy) void (^acknowledgementReceived)(); diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h index 46b17abceea..9c631638116 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h @@ -12,6 +12,7 @@ @property (nonatomic) bool delivered; @property (nonatomic) int64_t responseMessageId; @property (nonatomic) bool willInitializeApi; +@property (nonatomic) double sentTimestamp; - (instancetype)initWithMessageId:(int64_t)messageId messageSeqNo:(int32_t)messageSeqNo transactionId:(id)transactionId quickAckId:(int32_t)quickAckId; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h index 156fe128c33..9237e0d24d5 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h @@ -26,7 +26,7 @@ - (void)transportConnectionProblemsStatusChanged:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme hasConnectionProblems:(bool)hasConnectionProblems isProbablyHttp:(bool)isProbablyHttp; - (void)transportReadyForTransaction:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme transportSpecificTransaction:(MTMessageTransaction * _Nonnull)transportSpecificTransaction forceConfirmations:(bool)forceConfirmations transactionReady:(void (^ _Nonnull)(NSArray * _Nonnull))transactionReady; -- (void)transportHasIncomingData:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme data:(NSData * _Nonnull)data transactionId:(id _Nonnull)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^ _Nonnull)(id _Nonnull transactionId, bool success))decodeResult; +- (void)transportHasIncomingData:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme networkType:(int32_t)networkType data:(NSData * _Nonnull)data transactionId:(id _Nonnull)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^ _Nonnull)(id _Nonnull transactionId, bool success))decodeResult; - (void)transportTransactionsMayHaveFailed:(MTTransport * _Nonnull)transport transactionIds:(NSArray * _Nonnull)transactionIds; - (void)transportReceivedQuickAck:(MTTransport * _Nonnull)transport quickAckId:(int32_t)quickAckId; - (void)transportDecodeProgressToken:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme data:(NSData * _Nonnull)data token:(int64_t)token completion:(void (^ _Nonnull)(int64_t token, id _Nonnull progressToken))completion; @@ -57,7 +57,7 @@ - (void)stop; - (void)updateConnectionState; - (void)setDelegateNeedsTransaction; -- (void)_processIncomingData:(NSData * _Nonnull)data scheme:(MTTransportScheme * _Nonnull)scheme transactionId:(id _Nonnull)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^ _Nonnull)(id _Nonnull transactionId, bool success))decodeResult; +- (void)_processIncomingData:(NSData * _Nonnull)data scheme:(MTTransportScheme * _Nonnull)scheme networkType:(int32_t)networkType transactionId:(id _Nonnull)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^ _Nonnull)(id _Nonnull transactionId, bool success))decodeResult; - (void)_networkAvailabilityChanged:(bool)networkAvailable; - (void)activeTransactionIds:(void (^ _Nonnull)(NSArray * _Nonnull activeTransactionId))completion; diff --git a/submodules/MtProtoKit/Sources/GCDAsyncSocket.h b/submodules/MtProtoKit/Sources/GCDAsyncSocket.h index 1f7f6e63787..c329cd4b7da 100644 --- a/submodules/MtProtoKit/Sources/GCDAsyncSocket.h +++ b/submodules/MtProtoKit/Sources/GCDAsyncSocket.h @@ -975,7 +975,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * Called when a socket has completed reading the requested data into memory. * Not called if there is an error. **/ -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag networkType:(int32_t)networkType; /** * Called when a socket has read in data, but has not yet completed the read. diff --git a/submodules/MtProtoKit/Sources/GCDAsyncSocket.m b/submodules/MtProtoKit/Sources/GCDAsyncSocket.m index 470658d86e9..fd54f234a0b 100755 --- a/submodules/MtProtoKit/Sources/GCDAsyncSocket.m +++ b/submodules/MtProtoKit/Sources/GCDAsyncSocket.m @@ -5119,14 +5119,15 @@ - (void)completeCurrentRead result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO]; } - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadData:withTag:)]) + if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadData:withTag:networkType:)]) { __strong id theDelegate = delegate; GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer + int32_t networkType = _interface == MTNetworkUsageManagerInterfaceOther ? 0 : 1; dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate socket:self didReadData:result withTag:theRead->tag]; + [theDelegate socket:self didReadData:result withTag:theRead->tag networkType:networkType]; }}); } diff --git a/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m b/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m index be7b1f7ab24..5b79068a55c 100644 --- a/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m +++ b/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m @@ -313,7 +313,7 @@ + (MTSignal *)fetchConfigFromAddress:(MTBackupDatacenterAddress *)address curren __weak MTContext *weakCurrentContext = currentContext; return [[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) { - [request setCompleted:^(MTDatacenterAddressListData *result, __unused NSTimeInterval completionTimestamp, id error) + [request setCompleted:^(MTDatacenterAddressListData *result, __unused MTRequestResponseInfo *info, id error) { if (error == nil) { __strong MTContext *strongCurrentContext = weakCurrentContext; diff --git a/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m b/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m index 417818f43c1..7b339d497df 100644 --- a/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m +++ b/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m @@ -136,7 +136,7 @@ - (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(in } } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType { if ([message.body isKindOfClass:[MTRpcResultMessage class]]) { MTRpcResultMessage *rpcResultMessage = message.body; if (rpcResultMessage.requestMessageId == _currentMessageId) { diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m index 06c4f4264fd..882d6c6118f 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m @@ -410,7 +410,7 @@ - (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoS } } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType { if (_stage == MTDatacenterAuthStagePQ && [message.body isKindOfClass:[MTResPqMessage class]]) { diff --git a/submodules/MtProtoKit/Sources/MTDatacenterTransferAuthAction.m b/submodules/MtProtoKit/Sources/MTDatacenterTransferAuthAction.m index 54e2de78ad9..0e38f9c20cf 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterTransferAuthAction.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterTransferAuthAction.m @@ -101,7 +101,7 @@ - (void)beginTransferFromDatacenterId:(NSInteger)sourceDatacenterId [request setPayload:exportAuthRequestData metadata:@"exportAuthorization" shortMetadata:@"exportAuthorization" responseParser:responseParser]; __weak MTDatacenterTransferAuthAction *weakSelf = self; - [request setCompleted:^(MTExportedAuthorizationData *result, __unused NSTimeInterval timestamp, id error) + [request setCompleted:^(MTExportedAuthorizationData *result, __unused MTRequestResponseInfo *info, id error) { __strong MTDatacenterTransferAuthAction *strongSelf = weakSelf; if (strongSelf == nil) @@ -147,7 +147,7 @@ - (void)beginTransferWithId:(int64_t)dataId data:(NSData *)authData id authToken = _authToken; __weak MTDatacenterTransferAuthAction *weakSelf = self; - [request setCompleted:^(__unused id result, __unused NSTimeInterval timestamp, id error) + [request setCompleted:^(__unused id result, __unused MTRequestResponseInfo *info, id error) { __strong MTDatacenterTransferAuthAction *strongSelf = weakSelf; if (strongSelf == nil) diff --git a/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m b/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m index 8cd1ed0a6f9..2cac360ee8c 100644 --- a/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m +++ b/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m @@ -106,7 +106,7 @@ - (void)askForAnAddressDatacenterWithId:(NSInteger)targetDatacenterId useTempAut [request setPayload:getConfigData metadata:@"getConfig" shortMetadata:@"getConfig" responseParser:responseParser]; __weak MTDiscoverDatacenterAddressAction *weakSelf = self; - [request setCompleted:^(MTDatacenterAddressListData *result, __unused NSTimeInterval completionTimestamp, id error) + [request setCompleted:^(MTDatacenterAddressListData *result, __unused MTRequestResponseInfo *info, id error) { __strong MTDiscoverDatacenterAddressAction *strongSelf = weakSelf; if (strongSelf != nil) { diff --git a/submodules/MtProtoKit/Sources/MTProto.m b/submodules/MtProtoKit/Sources/MTProto.m index 4b02c06b507..7e71be1b65d 100644 --- a/submodules/MtProtoKit/Sources/MTProto.m +++ b/submodules/MtProtoKit/Sources/MTProto.m @@ -1966,7 +1966,7 @@ - (void)transportTransactionsSucceeded:(NSArray *)transactionIds return hexString; } -- (void)transportHasIncomingData:(MTTransport *)transport scheme:(MTTransportScheme *)scheme data:(NSData *)data transactionId:(id)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^)(id transactionId, bool success))decodeResult +- (void)transportHasIncomingData:(MTTransport *)transport scheme:(MTTransportScheme *)scheme networkType:(int32_t)networkType data:(NSData *)data transactionId:(id)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^)(id transactionId, bool success))decodeResult { /*__block bool simulateError = false; static dispatch_once_t onceToken; @@ -2097,7 +2097,7 @@ - (void)transportHasIncomingData:(MTTransport *)transport scheme:(MTTransportSch for (MTIncomingMessage *incomingMessage in parsedMessages) { - [self _processIncomingMessage:incomingMessage totalSize:(int)data.length withTransactionId:transactionId address:scheme.address authInfoSelector:authInfoSelector]; + [self _processIncomingMessage:incomingMessage totalSize:(int)data.length withTransactionId:transactionId address:scheme.address authInfoSelector:authInfoSelector networkType:networkType]; } if (requestTransactionAfterProcessing) @@ -2422,7 +2422,7 @@ - (NSArray *)_parseIncomingMessages:(NSData *)data dataMessageId:(out int64_t *) return messages; } -- (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:(int)totalSize withTransactionId:(id)transactionId address:(MTDatacenterAddress *)address authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:(int)totalSize withTransactionId:(id)transactionId address:(MTDatacenterAddress *)address authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType { if ([_sessionInfo messageProcessed:incomingMessage.messageId]) { @@ -2605,8 +2605,8 @@ - (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:( { id messageService = _messageServices[(NSUInteger)i]; - if ([messageService respondsToSelector:@selector(mtProto:receivedMessage:authInfoSelector:)]) - [messageService mtProto:self receivedMessage:incomingMessage authInfoSelector:authInfoSelector]; + if ([messageService respondsToSelector:@selector(mtProto:receivedMessage:authInfoSelector:networkType:)]) + [messageService mtProto:self receivedMessage:incomingMessage authInfoSelector:authInfoSelector networkType:networkType]; } if (_timeFixContext != nil && [incomingMessage.body isKindOfClass:[MTPongMessage class]] && ((MTPongMessage *)incomingMessage.body).messageId == _timeFixContext.messageId) diff --git a/submodules/MtProtoKit/Sources/MTRequest.m b/submodules/MtProtoKit/Sources/MTRequest.m index e348537a8c6..b154934fd38 100644 --- a/submodules/MtProtoKit/Sources/MTRequest.m +++ b/submodules/MtProtoKit/Sources/MTRequest.m @@ -5,6 +5,20 @@ #import #import +@implementation MTRequestResponseInfo + +- (instancetype)initWithNetworkType:(int32_t)networkType timestamp:(double)timestamp duration:(double)duration { + self = [super init]; + if (self != nil) { + _networkType = networkType; + _timestamp = timestamp; + _duration = duration; + } + return self; +} + +@end + @interface MTRequestInternalId : NSObject { NSUInteger _value; diff --git a/submodules/MtProtoKit/Sources/MTRequestMessageService.m b/submodules/MtProtoKit/Sources/MTRequestMessageService.m index 9b89b6a5451..fed278d302c 100644 --- a/submodules/MtProtoKit/Sources/MTRequestMessageService.m +++ b/submodules/MtProtoKit/Sources/MTRequestMessageService.m @@ -369,7 +369,7 @@ - (void)mtProtoApiEnvironmentUpdated:(MTProto *)mtProto apiEnvironment:(MTApiEnv MTRequestNoopParser responseParser = [[_context serialization] requestNoop:&noopData]; [request setPayload:noopData metadata:@"noop" shortMetadata:@"noop" responseParser:responseParser]; - [request setCompleted:^(__unused id result, __unused NSTimeInterval timestamp, __unused id error) { + [request setCompleted:^(__unused id result, __unused MTRequestResponseInfo *info, __unused id error) { }]; [self addRequest:request]; @@ -614,6 +614,7 @@ - (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoS } MTRequestContext *requestContext = [[MTRequestContext alloc] initWithMessageId:preparedMessage.messageId messageSeqNo:preparedMessage.seqNo transactionId:nil quickAckId:0]; + requestContext.sentTimestamp = CFAbsoluteTimeGetCurrent(); requestContext.willInitializeApi = requestsWillInitializeApi; requestContext.waitingForMessageId = true; request.requestContext = requestContext; @@ -646,6 +647,7 @@ - (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoS continue; } MTRequestContext *requestContext = [[MTRequestContext alloc] initWithMessageId:preparedMessage.messageId messageSeqNo:preparedMessage.seqNo transactionId:messageInternalIdToTransactionId[messageInternalId] quickAckId:(int32_t)[messageInternalIdToQuickAckId[messageInternalId] intValue]]; + requestContext.sentTimestamp = CFAbsoluteTimeGetCurrent(); requestContext.willInitializeApi = requestsWillInitializeApi; request.requestContext = requestContext; } @@ -667,7 +669,7 @@ - (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoS return nil; } -- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType { if ([message.body isKindOfClass:[MTRpcResultMessage class]]) { @@ -871,6 +873,8 @@ - (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage * } } + double sentTimestamp = request.requestContext.sentTimestamp; + request.requestContext = nil; if (restartRequest) @@ -879,11 +883,17 @@ - (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage * } else { - void (^completed)(id result, NSTimeInterval completionTimestamp, id error) = [request.completed copy]; + void (^completed)(id result, MTRequestResponseInfo *info, id error) = [request.completed copy]; [_requests removeObjectAtIndex:(NSUInteger)index]; - if (completed) - completed(rpcResult, message.timestamp, rpcError); + if (completed) { + double duration = 0.0; + if (sentTimestamp != 0.0) { + duration = CFAbsoluteTimeGetCurrent() - sentTimestamp; + } + MTRequestResponseInfo *info = [[MTRequestResponseInfo alloc] initWithNetworkType:networkType timestamp:message.timestamp duration:duration]; + completed(rpcResult, info, rpcError); + } } break; diff --git a/submodules/MtProtoKit/Sources/MTResendMessageService.m b/submodules/MtProtoKit/Sources/MTResendMessageService.m index 468e30eec68..c72d1f7f812 100644 --- a/submodules/MtProtoKit/Sources/MTResendMessageService.m +++ b/submodules/MtProtoKit/Sources/MTResendMessageService.m @@ -121,7 +121,7 @@ - (void)mtProtoAllTransactionsMayHaveFailed:(MTProto *)mtProto } } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType { if (message.messageId == _messageId) { diff --git a/submodules/MtProtoKit/Sources/MTTcpConnection.h b/submodules/MtProtoKit/Sources/MTTcpConnection.h index 872eea79819..b186b837453 100644 --- a/submodules/MtProtoKit/Sources/MTTcpConnection.h +++ b/submodules/MtProtoKit/Sources/MTTcpConnection.h @@ -13,7 +13,7 @@ - (void)tcpConnectionOpened:(MTTcpConnection *)connection; - (void)tcpConnectionClosed:(MTTcpConnection *)connection error:(bool)error; -- (void)tcpConnectionReceivedData:(MTTcpConnection *)connection data:(NSData *)data; +- (void)tcpConnectionReceivedData:(MTTcpConnection *)connection networkType:(int32_t)networkType data:(NSData *)data; - (void)tcpConnectionReceivedQuickAck:(MTTcpConnection *)connection quickAck:(int32_t)quickAck; - (void)tcpConnectionDecodePacketProgressToken:(MTTcpConnection *)connection data:(NSData *)data token:(int64_t)token completion:(void (^)(int64_t token, id packetProgressToken))completion; - (void)tcpConnectionProgressUpdated:(MTTcpConnection *)connection packetProgressToken:(id)packetProgressToken packetLength:(NSUInteger)packetLength progress:(float)progress; diff --git a/submodules/MtProtoKit/Sources/MTTcpConnection.m b/submodules/MtProtoKit/Sources/MTTcpConnection.m index 77819c28ec7..dd2755e68d1 100644 --- a/submodules/MtProtoKit/Sources/MTTcpConnection.m +++ b/submodules/MtProtoKit/Sources/MTTcpConnection.m @@ -664,10 +664,10 @@ - (void)socket:(GCDAsyncSocket *)socket didReadPartialDataOfLength:(NSUInteger)p } } -- (void)socket:(GCDAsyncSocket *)socket didReadData:(NSData *)rawData withTag:(long)tag { +- (void)socket:(GCDAsyncSocket *)socket didReadData:(NSData *)rawData withTag:(long)tag networkType:(int32_t)networkType { id delegate = _delegate; if (delegate) { - [delegate connectionInterfaceDidReadData:rawData withTag:tag]; + [delegate connectionInterfaceDidReadData:rawData withTag:tag networkType:networkType]; } } @@ -717,6 +717,7 @@ @interface MTTcpConnection () id _socket; bool _closed; + int32_t _lastNetworkType; bool _useIntermediateFormat; @@ -1459,7 +1460,7 @@ - (void)requestSocksConnection { [_socket readDataToLength:4 withTimeout:-1 tag:MTTcpSocksRequest]; } -- (void)connectionInterfaceDidReadData:(NSData *)rawData withTag:(long)tag +- (void)connectionInterfaceDidReadData:(NSData *)rawData withTag:(long)tag networkType:(int32_t)networkType { if (_closed) return; @@ -1754,12 +1755,12 @@ - (void)connectionInterfaceDidReadData:(NSData *)rawData withTag:(long)tag [_socket readDataToLength:(int)nextLength withTimeout:-1 tag:MTTcpSocksReceiveComplexPacketPart]; return; } else if (tag == MTTcpSocksReceiveComplexPacketPart) { - [self addReadData:rawData]; + [self addReadData:rawData networkType:networkType]; [_socket readDataToLength:5 withTimeout:-1 tag:MTTcpSocksReceiveComplexLength]; return; } else { - [self addReadData:rawData]; + [self addReadData:rawData networkType:networkType]; } } @@ -1775,15 +1776,15 @@ - (void)requestReadDataWithLength:(int)length tag:(int)tag { [_receivedDataBuffer replaceBytesInRange:NSMakeRange(0, _pendingReceiveData.length) withBytes:nil length:0]; int tag = _pendingReceiveData.tag; _pendingReceiveData = nil; - [self processReceivedData:rawData tag:tag]; + [self processReceivedData:rawData tag:tag networkType:_lastNetworkType]; } } -- (void)addReadData:(NSData *)data { +- (void)addReadData:(NSData *)data networkType:(int32_t)networkType { if (_pendingReceiveData != nil && _pendingReceiveData.length == data.length) { int tag = _pendingReceiveData.tag; _pendingReceiveData = nil; - [self processReceivedData:data tag:tag]; + [self processReceivedData:data tag:tag networkType:networkType]; } else { [_receivedDataBuffer appendData:data]; if (_pendingReceiveData != nil) { @@ -1792,13 +1793,15 @@ - (void)addReadData:(NSData *)data { [_receivedDataBuffer replaceBytesInRange:NSMakeRange(0, _pendingReceiveData.length) withBytes:nil length:0]; int tag = _pendingReceiveData.tag; _pendingReceiveData = nil; - [self processReceivedData:rawData tag:tag]; + [self processReceivedData:rawData tag:tag networkType:networkType]; } } } } -- (void)processReceivedData:(NSData *)rawData tag:(int)tag { +- (void)processReceivedData:(NSData *)rawData tag:(int)tag networkType:(int32_t)networkType { + _lastNetworkType = networkType; + NSMutableData *decryptedData = [[NSMutableData alloc] initWithLength:rawData.length]; [_incomingAesCtr encryptIn:rawData.bytes out:decryptedData.mutableBytes len:rawData.length]; @@ -1968,8 +1971,8 @@ - (void)processReceivedData:(NSData *)rawData tag:(int)tag { if (_connectionReceivedData) _connectionReceivedData(packetData); id delegate = _delegate; - if ([delegate respondsToSelector:@selector(tcpConnectionReceivedData:data:)]) - [delegate tcpConnectionReceivedData:self data:packetData]; + if ([delegate respondsToSelector:@selector(tcpConnectionReceivedData:networkType:data:)]) + [delegate tcpConnectionReceivedData:self networkType:networkType data:packetData]; } if (_useIntermediateFormat) { diff --git a/submodules/MtProtoKit/Sources/MTTcpTransport.m b/submodules/MtProtoKit/Sources/MTTcpTransport.m index 5dc5154d52f..7c5b0fab46b 100644 --- a/submodules/MtProtoKit/Sources/MTTcpTransport.m +++ b/submodules/MtProtoKit/Sources/MTTcpTransport.m @@ -452,7 +452,7 @@ - (void)tcpConnectionClosed:(MTTcpConnection *)connection error:(bool)error }]; } -- (void)tcpConnectionReceivedData:(MTTcpConnection *)connection data:(NSData *)data +- (void)tcpConnectionReceivedData:(MTTcpConnection *)connection networkType:(int32_t)networkType data:(NSData *)data { MTTcpTransportContext *transportContext = _transportContext; [[MTTcpTransport tcpTransportQueue] dispatchOnQueue:^ @@ -464,7 +464,7 @@ - (void)tcpConnectionReceivedData:(MTTcpConnection *)connection data:(NSData *)d [self startActualizationPingResendTimer]; __weak MTTcpTransport *weakSelf = self; - [self _processIncomingData:data scheme:connection.scheme transactionId:connection.internalId requestTransactionAfterProcessing:false decodeResult:^(id transactionId, bool success) + [self _processIncomingData:data scheme:connection.scheme networkType:networkType transactionId:connection.internalId requestTransactionAfterProcessing:false decodeResult:^(id transactionId, bool success) { if (success) { @@ -760,7 +760,7 @@ - (void)mtProtoServerDidChangeSession:(MTProto *)__unused mtProto firstValidMess }]; } -- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType { if ([incomingMessage.body isKindOfClass:[MTPongMessage class]]) { diff --git a/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m b/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m index 30f1fe2f152..deaa634fd26 100644 --- a/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m +++ b/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m @@ -127,7 +127,7 @@ - (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(in } } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType { if ([message.body isKindOfClass:[MTFutureSaltsMessage class]] && ((MTFutureSaltsMessage *)message.body).requestMessageId == _currentMessageId) { diff --git a/submodules/MtProtoKit/Sources/MTTransport.m b/submodules/MtProtoKit/Sources/MTTransport.m index ab2511226f4..6d23ff4897b 100644 --- a/submodules/MtProtoKit/Sources/MTTransport.m +++ b/submodules/MtProtoKit/Sources/MTTransport.m @@ -58,12 +58,12 @@ - (void)setDelegateNeedsTransaction { } -- (void)_processIncomingData:(NSData *)data scheme:(MTTransportScheme *)scheme transactionId:(id)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^)(id transactionId, bool success))decodeResult +- (void)_processIncomingData:(NSData *)data scheme:(MTTransportScheme *)scheme networkType:(int32_t)networkType transactionId:(id)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^)(id transactionId, bool success))decodeResult { id delegate = _delegate; - if ([delegate respondsToSelector:@selector(transportHasIncomingData:scheme:data:transactionId:requestTransactionAfterProcessing:decodeResult:)]) + if ([delegate respondsToSelector:@selector(transportHasIncomingData:scheme:networkType:data:transactionId:requestTransactionAfterProcessing:decodeResult:)]) { - [delegate transportHasIncomingData:self scheme:scheme data:data transactionId:transactionId requestTransactionAfterProcessing:requestTransactionAfterProcessing decodeResult:decodeResult]; + [delegate transportHasIncomingData:self scheme:scheme networkType:networkType data:data transactionId:transactionId requestTransactionAfterProcessing:requestTransactionAfterProcessing decodeResult:decodeResult]; } } diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 1b48a4be4c2..2362832a498 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -970,6 +970,8 @@ public class Account { private var lastSmallLogPostTimestamp: Double? private let smallLogPostDisposable = MetaDisposable() + let networkStatsContext: NetworkStatsContext + public init(accountManager: AccountManager, id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, networkArguments: NetworkInitializationArguments, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, supplementary: Bool) { self.accountManager = accountManager self.id = id @@ -983,6 +985,8 @@ public class Account { self.auxiliaryMethods = auxiliaryMethods self.supplementary = supplementary + self.networkStatsContext = NetworkStatsContext(postbox: postbox) + self.peerInputActivityManager = PeerInputActivityManager() self.callSessionManager = CallSessionManager(postbox: postbox, network: network, maxLayer: networkArguments.voipMaxLayer, versions: networkArguments.voipVersions, addUpdates: { [weak self] updates in self?.stateManager?.addUpdates(updates) diff --git a/submodules/TelegramCore/Sources/Network/Download.swift b/submodules/TelegramCore/Sources/Network/Download.swift index 477be727dee..ff00e742512 100644 --- a/submodules/TelegramCore/Sources/Network/Download.swift +++ b/submodules/TelegramCore/Sources/Network/Download.swift @@ -362,16 +362,16 @@ class Download: NSObject, MTRequestMessageServiceDelegate { return true } - request.completed = { (boxedResponse, timestamp, error) -> () in + request.completed = { (boxedResponse, info, error) -> () in if let error = error { - subscriber.putError((error, timestamp)) + subscriber.putError((error, info?.timestamp ?? 0.0)) } else { if let result = (boxedResponse as! BoxedMessage).body as? T { - subscriber.putNext((result, timestamp)) + subscriber.putNext((result, info?.timestamp ?? 0.0)) subscriber.putCompletion() } else { - subscriber.putError((MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"), timestamp)) + subscriber.putError((MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"), info?.timestamp ?? 0.0)) } } } @@ -386,7 +386,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { } } - func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "") -> Signal<(Any, Double), (MTRpcError, Double)> { + func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "") -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() @@ -414,11 +414,16 @@ class Download: NSObject, MTRequestMessageServiceDelegate { return true } - request.completed = { (boxedResponse, timestamp, error) -> () in + request.completed = { (boxedResponse, info, error) -> () in if let error = error { - subscriber.putError((error, timestamp)) + subscriber.putError((error, info?.timestamp ?? 0)) } else { - subscriber.putNext(((boxedResponse as! BoxedMessage).body, timestamp)) + let mappedInfo = NetworkResponseInfo( + timestamp: info?.timestamp ?? 0.0, + networkType: info?.networkType == 0 ? .wifi : .cellular, + networkDuration: info?.duration ?? 0.0 + ) + subscriber.putNext(((boxedResponse as! BoxedMessage).body, mappedInfo)) subscriber.putCompletion() } } diff --git a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift index 568eb5fac43..3bd4c3dbe0e 100644 --- a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift +++ b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift @@ -86,14 +86,17 @@ private struct DownloadWrapper { let network: Network let useMainConnection: Bool - func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool) -> Signal { + func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool) -> Signal<(T, NetworkResponseInfo), MTRpcError> { let target: MultiplexedRequestTarget if self.isCdn { target = .cdn(Int(self.datacenterId)) } else { target = .main(Int(self.datacenterId)) } - return network.multiplexedRequestManager.request(to: target, consumerId: self.consumerId, data: data, tag: tag, continueInBackground: continueInBackground) + return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, data: data, tag: tag, continueInBackground: continueInBackground) + |> mapError { error, _ -> MTRpcError in + return error + } } } @@ -172,7 +175,7 @@ private final class MultipartCdnHashSource { self.clusterContexts[offset] = clusterContext disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground) - |> map { partHashes -> [Int64: Data] in + |> map { partHashes, _ -> [Int64: Data] in var parsedPartHashes: [Int64: Data] = [:] for part in partHashes { switch part { @@ -288,9 +291,20 @@ private final class MultipartCdnHashSource { private enum MultipartFetchSource { case none case master(location: MultipartFetchMasterLocation, download: DownloadWrapper) - case cdn(masterDatacenterId: Int32, fileToken: Data, key: Data, iv: Data, download: DownloadWrapper, masterDownload: DownloadWrapper, hashSource: MultipartCdnHashSource) + case cdn(masterDatacenterId: Int32, cdnDatacenterId: Int32, fileToken: Data, key: Data, iv: Data, download: DownloadWrapper, masterDownload: DownloadWrapper, hashSource: MultipartCdnHashSource) - func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> Signal { + var effectiveDatacenterId: Int32 { + switch self { + case .none: + return 0 + case let .master(location, _): + return location.datacenterId + case let .cdn(_, cdnDatacenterId, _, _, _, _, _, _): + return cdnDatacenterId + } + } + + func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> { var resourceReferenceValue: MediaResourceReference? switch resourceReference { case .forceRevalidate: @@ -323,14 +337,14 @@ private enum MultipartFetchSource { } return .generic } - |> mapToSignal { result -> Signal in + |> mapToSignal { result, info -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> in switch result { case let .file(_, _, bytes): var resultData = bytes.makeData() if resultData.count > Int(limit) { resultData.count = Int(limit) } - return .single(resultData) + return .single((resultData, info)) case let .fileCdnRedirect(dcId, fileToken, encryptionKey, encryptionIv, partHashes): var parsedPartHashes: [Int64: Data] = [:] for part in partHashes { @@ -350,18 +364,18 @@ private enum MultipartFetchSource { |> mapError { error -> MultipartFetchDownloadError in return .fatal } - |> mapToSignal { result -> Signal in + |> mapToSignal { result, info -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> in switch result { case let .webFile(_, _, _, _, bytes): var resultData = bytes.makeData() if resultData.count > Int(limit) { resultData.count = Int(limit) } - return .single(resultData) + return .single((resultData, info)) } } } - case let .cdn(masterDatacenterId, fileToken, key, iv, download, _, hashSource): + case let .cdn(masterDatacenterId, _, fileToken, key, iv, download, _, hashSource): var updatedLength = roundUp(Int64(limit), to: 4096) while updatedLength % 4096 != 0 || 1048576 % updatedLength != 0 { updatedLength += 1 @@ -371,13 +385,13 @@ private enum MultipartFetchSource { |> mapError { _ -> MultipartFetchDownloadError in return .generic } - |> mapToSignal { result -> Signal in + |> mapToSignal { result, info -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> in switch result { case let .cdnFileReuploadNeeded(token): return .fail(.reuploadToCdn(masterDatacenterId: masterDatacenterId, token: token.makeData())) case let .cdnFile(bytes): if bytes.size == 0 { - return .single(bytes.makeData()) + return .single((bytes.makeData(), info)) } else { var partIv = iv let partIvCount = partIv.count @@ -386,13 +400,14 @@ private enum MultipartFetchSource { var ivOffset: Int32 = Int32(clamping: (offset / 16)).bigEndian memcpy(bytes.advanced(by: partIvCount - 4), &ivOffset, 4) } - return .single(MTAesCtrDecrypt(bytes.makeData(), key, partIv)!) + return .single((MTAesCtrDecrypt(bytes.makeData(), key, partIv)!, info)) } } } return combineLatest(part, hashSource.get(offset: offset, limit: limit)) - |> mapToSignal { partData, hashData -> Signal in + |> mapToSignal { partDataAndInfo, hashData -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> in + let (partData, info) = partDataAndInfo var localOffset: Int64 = 0 while localOffset < partData.count { let dataToHash = partData.subdata(in: Int(localOffset) ..< min(partData.count, Int(localOffset + Int64(dataHashLength)))) @@ -407,7 +422,7 @@ private enum MultipartFetchSource { localOffset += Int64(dataHashLength) } - return .single(partData) + return .single((partData, info)) } } } @@ -425,6 +440,21 @@ private final class MultipartFetchManager { var byteCount: Int } + private final class FetchingPart { + let size: Int64 + let disposable: Disposable + let startTime: Double + + init( + size: Int64, + disposable: Disposable + ) { + self.size = size + self.disposable = disposable + self.startTime = CFAbsoluteTimeGetCurrent() + } + } + let parallelParts: Int let defaultPartSize: Int64 var partAlignment: Int64 = 4 * 1024 @@ -445,6 +475,7 @@ private final class MultipartFetchManager { let postbox: Postbox let network: Network + let networkStatsContext: NetworkStatsContext? let revalidationContext: MediaReferenceRevalidationContext? let continueInBackground: Bool let partReady: (Int64, Data) -> Void @@ -454,7 +485,7 @@ private final class MultipartFetchManager { private let useMainConnection: Bool private var source: MultipartFetchSource - var fetchingParts: [Int64: (Int64, Disposable)] = [:] + private var fetchingParts: [Int64: FetchingPart] = [:] var nextFetchingPartId = 0 var fetchedParts: [Int64: (Int64, Data)] = [:] var cachedPartHashes: [Int64: Data] = [:] @@ -474,7 +505,7 @@ private final class MultipartFetchManager { private var fetchSpeedRecords: [FetchSpeedRecord] = [] private var totalFetchedByteCount: Int = 0 - init(resource: TelegramMediaResource, parameters: MediaResourceFetchParameters?, size: Int64?, intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError>, encryptionKey: SecretFileEncryptionKey?, decryptedSize: Int64?, location: MultipartFetchMasterLocation, postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext?, partReady: @escaping (Int64, Data) -> Void, reportCompleteSize: @escaping (Int64) -> Void, finishWithError: @escaping (MediaResourceDataFetchError) -> Void, useMainConnection: Bool) { + init(resource: TelegramMediaResource, parameters: MediaResourceFetchParameters?, size: Int64?, intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError>, encryptionKey: SecretFileEncryptionKey?, decryptedSize: Int64?, location: MultipartFetchMasterLocation, postbox: Postbox, network: Network, networkStatsContext: NetworkStatsContext?, revalidationContext: MediaReferenceRevalidationContext?, partReady: @escaping (Int64, Data) -> Void, reportCompleteSize: @escaping (Int64) -> Void, finishWithError: @escaping (MediaResourceDataFetchError) -> Void, useMainConnection: Bool) { self.resource = resource self.parameters = parameters self.consumerId = Int64.random(in: Int64.min ... Int64.max) @@ -526,6 +557,7 @@ private final class MultipartFetchManager { self.state = MultipartDownloadState(encryptionKey: encryptionKey, decryptedSize: decryptedSize) self.postbox = postbox self.network = network + self.networkStatsContext = networkStatsContext self.revalidationContext = revalidationContext self.source = .master(location: location, download: DownloadWrapper(consumerId: self.consumerId, datacenterId: location.datacenterId, isCdn: false, network: network, useMainConnection: self.useMainConnection)) self.partReady = partReady @@ -613,8 +645,8 @@ private final class MultipartFetchManager { func cancel() { self.queue.async { self.source = .none - for (_, (_, disposable)) in self.fetchingParts { - disposable.dispose() + for (_, fetchingPart) in self.fetchingParts { + fetchingPart.disposable.dispose() } self.reuploadToCdnDisposable.dispose() self.revalidateMediaReferenceDisposable.dispose() @@ -680,8 +712,8 @@ private final class MultipartFetchManager { } } - for (offset, (size, _)) in self.fetchingParts { - removeFromFetchIntervals.formUnion(RangeSet(offset ..< (offset + size))) + for (offset, fetchingPart) in self.fetchingParts { + removeFromFetchIntervals.formUnion(RangeSet(offset ..< (offset + fetchingPart.size))) } if let completeSize = self.completeSize { @@ -758,16 +790,27 @@ private final class MultipartFetchManager { insertIndex += 1 } + let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound) let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground) - //|> delay(5.0, queue: self.queue) |> deliverOn(self.queue) let partDisposable = MetaDisposable() - self.fetchingParts[downloadRange.lowerBound] = (Int64(downloadRange.count), partDisposable) - - partDisposable.set(part.start(next: { [weak self] data in + self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable) + let partStartTimestamp = CFAbsoluteTimeGetCurrent() + let effectiveDatacenterId = self.source.effectiveDatacenterId + partDisposable.set(part.start(next: { [weak self] data, info in guard let strongSelf = self else { return } + + strongSelf.networkStatsContext?.add(downloadEvents: [ + NetworkStatsContext.DownloadEvent( + networkType: info.networkType, + datacenterId: effectiveDatacenterId, + size: Double(partSize), + networkDuration: info.networkDuration, + issueDuration: CFAbsoluteTimeGetCurrent() - partStartTimestamp + ) + ]) if data.count < downloadRange.count { strongSelf.completeSize = downloadRange.lowerBound + Int64(data.count) } @@ -788,7 +831,7 @@ private final class MultipartFetchManager { if !strongSelf.revalidatingMediaReference && !strongSelf.revalidatedMediaReference { strongSelf.revalidatingMediaReference = true for (_, part) in strongSelf.fetchingParts { - part.1.dispose() + part.disposable.dispose() } strongSelf.fetchingParts.removeAll() @@ -819,7 +862,7 @@ private final class MultipartFetchManager { switch strongSelf.source { case let .master(location, download): strongSelf.partAlignment = dataHashLength - strongSelf.source = .cdn(masterDatacenterId: location.datacenterId, fileToken: token, key: key, iv: iv, download: DownloadWrapper(consumerId: strongSelf.consumerId, datacenterId: id, isCdn: true, network: strongSelf.network, useMainConnection: strongSelf.useMainConnection), masterDownload: download, hashSource: MultipartCdnHashSource(queue: strongSelf.queue, fileToken: token, hashes: partHashes, masterDownload: download, continueInBackground: strongSelf.continueInBackground)) + strongSelf.source = .cdn(masterDatacenterId: location.datacenterId, cdnDatacenterId: id, fileToken: token, key: key, iv: iv, download: DownloadWrapper(consumerId: strongSelf.consumerId, datacenterId: id, isCdn: true, network: strongSelf.network, useMainConnection: strongSelf.useMainConnection), masterDownload: download, hashSource: MultipartCdnHashSource(queue: strongSelf.queue, fileToken: token, hashes: partHashes, masterDownload: download, continueInBackground: strongSelf.continueInBackground)) strongSelf.checkState() case .cdn, .none: break @@ -828,10 +871,13 @@ private final class MultipartFetchManager { switch strongSelf.source { case .master, .none: break - case let .cdn(_, fileToken, _, _, _, masterDownload, _): + case let .cdn(_, _, fileToken, _, _, _, masterDownload, _): if !strongSelf.reuploadingToCdn { strongSelf.reuploadingToCdn = true let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground) + |> map { result, _ -> [Api.FileHash] in + return result + } |> `catch` { _ -> Signal<[Api.FileHash], NoError> in return .single([]) } @@ -856,6 +902,7 @@ public func standaloneMultipartFetch(postbox: Postbox, network: Network, resourc postbox: postbox, network: network, mediaReferenceRevalidationContext: nil, + networkStatsContext: nil, resource: resource, datacenterId: datacenterId, size: size, @@ -877,6 +924,7 @@ private func multipartFetchV1( postbox: Postbox, network: Network, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext?, + networkStatsContext: NetworkStatsContext?, resource: TelegramMediaResource, datacenterId: Int, size: Int64?, @@ -944,7 +992,7 @@ private func multipartFetchV1( subscriber.putNext(.reset) } - let manager = MultipartFetchManager(resource: resource, parameters: parameters, size: size, intervals: intervals, encryptionKey: encryptionKey, decryptedSize: decryptedSize, location: location, postbox: postbox, network: network, revalidationContext: mediaReferenceRevalidationContext, partReady: { dataOffset, data in + let manager = MultipartFetchManager(resource: resource, parameters: parameters, size: size, intervals: intervals, encryptionKey: encryptionKey, decryptedSize: decryptedSize, location: location, postbox: postbox, network: network, networkStatsContext: networkStatsContext, revalidationContext: mediaReferenceRevalidationContext, partReady: { dataOffset, data in subscriber.putNext(.dataPart(resourceOffset: dataOffset, data: data, range: 0 ..< Int64(data.count), complete: false)) }, reportCompleteSize: { size in subscriber.putNext(.resourceSizeUpdated(size)) @@ -968,6 +1016,7 @@ func multipartFetch( postbox: Postbox, network: Network, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext?, + networkStatsContext: NetworkStatsContext?, resource: TelegramMediaResource, datacenterId: Int, size: Int64?, @@ -998,6 +1047,7 @@ func multipartFetch( postbox: postbox, network: network, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, + networkStatsContext: networkStatsContext, resource: resource, datacenterId: datacenterId, size: size, diff --git a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift index 16c1c22e642..73b7d0c2c88 100644 --- a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift +++ b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift @@ -33,10 +33,10 @@ private final class RequestData { let continueInBackground: Bool let automaticFloodWait: Bool let deserializeResponse: (Buffer) -> Any? - let completed: (Any, Double) -> Void + let completed: (Any, NetworkResponseInfo) -> Void let error: (MTRpcError, Double) -> Void - init(id: Int32, consumerId: Int64, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, Double) -> Void, error: @escaping (MTRpcError, Double) -> Void) { + init(id: Int32, consumerId: Int64, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) { self.id = id self.consumerId = consumerId self.target = target @@ -80,6 +80,11 @@ private struct MultiplexedRequestTargetTimerKey: Equatable, Hashable { private typealias SignalKitTimer = SwiftSignalKit.Timer +struct NetworkResponseInfo { + var timestamp: Double + var networkType: NetworkStatsContext.NetworkType + var networkDuration: Double +} private final class MultiplexedRequestManagerContext { private let queue: Queue @@ -109,15 +114,15 @@ private final class MultiplexedRequestManagerContext { } } - func request(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, completed: @escaping (Any, Double) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable { + func request(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable { let targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground) let requestId = self.nextId self.nextId += 1 self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, deserializeResponse: { buffer in return data.2(buffer) - }, completed: { result, timestamp in - completed(result, timestamp) + }, completed: { result, info in + completed(result, info) }, error: { e, timestamp in error(e, timestamp) })) @@ -189,7 +194,7 @@ private final class MultiplexedRequestManagerContext { let requestId = request.id selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable)) let queue = self.queue - disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait).start(next: { [weak self, weak selectedContext] result, timestamp in + disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait).start(next: { [weak self, weak selectedContext] result, info in queue.async { guard let strongSelf = self else { return @@ -202,7 +207,7 @@ private final class MultiplexedRequestManagerContext { } } } - request.completed(result, timestamp) + request.completed(result, info) strongSelf.updateState() } }, error: { [weak self, weak selectedContext] error, timestamp in @@ -299,18 +304,18 @@ final class MultiplexedRequestManager { } } - func requestWithAdditionalInfo(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true) -> Signal<(T, Double), (MTRpcError, Double)> { + func requestWithAdditionalInfo(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> { return Signal { subscriber in let disposable = MetaDisposable() self.context.with { context in disposable.set(context.request(to: target, consumerId: consumerId, data: (data.0, data.1, { buffer in return data.2.parse(buffer) - }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, completed: { result, timestamp in + }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, completed: { result, info in if let result = result as? T { - subscriber.putNext((result, timestamp)) + subscriber.putNext((result, info)) subscriber.putCompletion() } else { - subscriber.putError((MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"), timestamp)) + subscriber.putError((MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"), info.timestamp)) } }, error: { error, timestamp in subscriber.putError((error, timestamp)) diff --git a/submodules/TelegramCore/Sources/Network/NetworkFrameworkTcpConnectionInterface.swift b/submodules/TelegramCore/Sources/Network/NetworkFrameworkTcpConnectionInterface.swift index 7cd4f879585..dd1def59745 100644 --- a/submodules/TelegramCore/Sources/Network/NetworkFrameworkTcpConnectionInterface.swift +++ b/submodules/TelegramCore/Sources/Network/NetworkFrameworkTcpConnectionInterface.swift @@ -222,9 +222,10 @@ final class NetworkFrameworkTcpConnectionInterface: NSObject, MTTcpConnectionInt self.currentReadRequest = nil weak var delegate = self.delegate + let currentInterfaceIsWifi = self.currentInterfaceIsWifi self.delegateQueue.async { if let delegate = delegate { - delegate.connectionInterfaceDidRead(currentReadRequest.data, withTag: currentReadRequest.request.tag) + delegate.connectionInterfaceDidRead(currentReadRequest.data, withTag: currentReadRequest.request.tag, networkType: currentInterfaceIsWifi ? 0 : 1) } } diff --git a/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift b/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift new file mode 100644 index 00000000000..21b656fed57 --- /dev/null +++ b/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift @@ -0,0 +1,122 @@ +import Foundation +import SwiftSignalKit +import Postbox + +final class NetworkStatsContext { + enum NetworkType: Int32 { + case wifi = 0 + case cellular = 1 + } + + struct DownloadEvent { + let networkType: NetworkType + let datacenterId: Int32 + let size: Double + let networkDuration: Double + let issueDuration: Double + + init( + networkType: NetworkType, + datacenterId: Int32, + size: Double, + networkDuration: Double, + issueDuration: Double + ) { + self.networkType = networkType + self.datacenterId = datacenterId + self.size = size + self.networkDuration = networkDuration + self.issueDuration = issueDuration + } + } + + private struct TargetKey: Hashable { + let networkType: NetworkType + let datacenterId: Int32 + + init(networkType: NetworkType, datacenterId: Int32) { + self.networkType = networkType + self.datacenterId = datacenterId + } + } + + private final class AverageStats { + var networkBps: Double = 0.0 + var issueDuration: Double = 0.0 + var networkDelay: Double = 0.0 + var count: Int = 0 + var size: Int64 = 0 + } + + private final class Impl { + let queue: Queue + let postbox: Postbox + + var averageTargetStats: [TargetKey: AverageStats] = [:] + + init(queue: Queue, postbox: Postbox) { + self.queue = queue + self.postbox = postbox + } + + func add(downloadEvents: [DownloadEvent]) { + for event in downloadEvents { + if event.networkDuration == 0.0 { + continue + } + let targetKey = TargetKey(networkType: event.networkType, datacenterId: event.datacenterId) + let averageStats: AverageStats + if let current = self.averageTargetStats[targetKey] { + averageStats = current + } else { + averageStats = AverageStats() + self.averageTargetStats[targetKey] = averageStats + } + averageStats.count += 1 + averageStats.issueDuration += event.issueDuration + averageStats.networkDelay += event.issueDuration - event.networkDuration + averageStats.networkBps += event.size / event.networkDuration + averageStats.size += Int64(event.size) + } + + self.maybeFlushStats() + } + + private func maybeFlushStats() { + var removeKeys: [TargetKey] = [] + for (targetKey, averageStats) in self.averageTargetStats { + if averageStats.count >= 1000 || averageStats.size >= 4 * 1024 * 1024 { + addAppLogEvent(postbox: self.postbox, type: "download", data: .dictionary([ + "n": .number(Double(targetKey.networkType.rawValue)), + "d": .number(Double(targetKey.datacenterId)), + "b": .number(averageStats.networkBps / Double(averageStats.count)), + "nd": .number(averageStats.networkDelay / Double(averageStats.count)) + ])) + removeKeys.append(targetKey) + } + } + for key in removeKeys { + self.averageTargetStats.removeValue(forKey: key) + } + } + } + + private static let sharedQueue = Queue(name: "NetworkStatsContext") + + private let queue: Queue + private let impl: QueueLocalObject + + init(postbox: Postbox) { + let queue = NetworkStatsContext.sharedQueue + self.queue = queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue, postbox: postbox) + }) + } + + func add(downloadEvents: [DownloadEvent]) { + self.impl.with { impl in + impl.add(downloadEvents: downloadEvents) + } + } +} diff --git a/submodules/TelegramCore/Sources/State/Fetch.swift b/submodules/TelegramCore/Sources/State/Fetch.swift index 5e65c431971..831354282a4 100644 --- a/submodules/TelegramCore/Sources/State/Fetch.swift +++ b/submodules/TelegramCore/Sources/State/Fetch.swift @@ -23,7 +23,7 @@ private final class MediaResourceDataCopyFile : MediaResourceDataFetchCopyLocalI } public func fetchCloudMediaLocation(account: Account, resource: TelegramMediaResource, datacenterId: Int, size: Int64?, intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError>, parameters: MediaResourceFetchParameters?) -> Signal { - return multipartFetch(postbox: account.postbox, network: account.network, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, resource: resource, datacenterId: datacenterId, size: size, intervals: intervals, parameters: parameters) + return multipartFetch(postbox: account.postbox, network: account.network, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, networkStatsContext: account.networkStatsContext, resource: resource, datacenterId: datacenterId, size: size, intervals: intervals, parameters: parameters) } private func fetchLocalFileResource(path: String, move: Bool) -> Signal { diff --git a/submodules/TelegramCore/Sources/State/FetchSecretFileResource.swift b/submodules/TelegramCore/Sources/State/FetchSecretFileResource.swift index c352f451844..13491cbbf36 100644 --- a/submodules/TelegramCore/Sources/State/FetchSecretFileResource.swift +++ b/submodules/TelegramCore/Sources/State/FetchSecretFileResource.swift @@ -5,5 +5,5 @@ import MtProtoKit func fetchSecretFileResource(account: Account, resource: SecretFileMediaResource, intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError>, parameters: MediaResourceFetchParameters?) -> Signal { - return multipartFetch(postbox: account.postbox, network: account.network, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, resource: resource, datacenterId: resource.datacenterId, size: resource.size, intervals: intervals, parameters: parameters, encryptionKey: resource.key, decryptedSize: resource.decryptedSize) + return multipartFetch(postbox: account.postbox, network: account.network, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, networkStatsContext: account.networkStatsContext, resource: resource, datacenterId: resource.datacenterId, size: resource.size, intervals: intervals, parameters: parameters, encryptionKey: resource.key, decryptedSize: resource.decryptedSize) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index e71bff8e580..7d286c32896 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -339,7 +339,7 @@ public extension TelegramEngine { |> mapToSignal { datacenterId -> Signal in let resource = AlbumCoverResource(datacenterId: Int(datacenterId), file: file, title: title, performer: performer, isThumbnail: isThumbnail) - return multipartFetch(postbox: self.account.postbox, network: self.account.network, mediaReferenceRevalidationContext: self.account.mediaReferenceRevalidationContext, resource: resource, datacenterId: Int(datacenterId), size: nil, intervals: .single([(0 ..< Int64.max, .default)]), parameters: MediaResourceFetchParameters( + return multipartFetch(postbox: self.account.postbox, network: self.account.network, mediaReferenceRevalidationContext: self.account.mediaReferenceRevalidationContext, networkStatsContext: self.account.networkStatsContext, resource: resource, datacenterId: Int(datacenterId), size: nil, intervals: .single([(0 ..< Int64.max, .default)]), parameters: MediaResourceFetchParameters( tag: nil, info: TelegramCloudMediaResourceFetchInfo( reference: MediaResourceReference.standalone(resource: resource), From ce40309c546b5931027aca71503b9634ef821195 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 7 Apr 2023 18:34:09 +0400 Subject: [PATCH 14/57] Add debug logs --- .../Sources/TelegramEngine/Peers/TelegramEnginePeers.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index c51b687b89f..ba0442c7c5e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -657,6 +657,7 @@ public extension TelegramEngine { } public func getNextUnreadChannel(peerId: PeerId, chatListFilterId: Int32?, getFilterPredicate: @escaping (ChatListFilterData) -> ChatListFilterPredicate) -> Signal<(peer: EnginePeer, unreadCount: Int, location: NextUnreadChannelLocation)?, NoError> { + let startTime = CFAbsoluteTimeGetCurrent() return self.account.postbox.transaction { transaction -> (peer: EnginePeer, unreadCount: Int, location: NextUnreadChannelLocation)? in func getForFilter(predicate: ChatListFilterPredicate?, isArchived: Bool) -> (peer: EnginePeer, unreadCount: Int)? { let additionalFilter: (Peer) -> Bool = { peer in @@ -756,6 +757,12 @@ public extension TelegramEngine { return nil } } + |> beforeNext { _ in + let delayTime = CFAbsoluteTimeGetCurrent() - startTime + if delayTime > 0.3 { + Logger.shared.log("getNextUnreadChannel", "took \(delayTime) s") + } + } } public func getOpaqueChatInterfaceState(peerId: PeerId, threadId: Int64?) -> Signal { From e70ac493114cce1983b8ffae9f0e7b858477381d Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 7 Apr 2023 22:55:15 +0400 Subject: [PATCH 15/57] Fix method signature --- .../Sources/State/UnauthorizedAccountStateManager.swift | 2 +- .../TelegramCore/Sources/State/UpdateMessageService.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramCore/Sources/State/UnauthorizedAccountStateManager.swift b/submodules/TelegramCore/Sources/State/UnauthorizedAccountStateManager.swift index 7feccde0c86..d2b8582bdb4 100644 --- a/submodules/TelegramCore/Sources/State/UnauthorizedAccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/UnauthorizedAccountStateManager.swift @@ -26,7 +26,7 @@ private final class UnauthorizedUpdateMessageService: NSObject, MTMessageService self.pipe.putNext(updates) } - func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector) { + func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector, networkType: Int32) { if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates { self.addUpdates(updates) } diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 7778a947fcb..ea5a7ddb52c 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -34,7 +34,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.pipe.putNext(groups) } - func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector) { + func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector, networkType: Int32) { if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates { self.addUpdates(updates) } From 1e2ed2d2f4e3b0d66884ce5f412a4ee0bd43adff Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 8 Apr 2023 20:10:49 +0400 Subject: [PATCH 16/57] Chat wallpaper improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 7 +- .../Sources/CalendarMessageScreen.swift | 2 +- .../Sources/ChatListController.swift | 2 +- .../Sources/LocationViewControllerNode.swift | 2 +- .../Sources/MediaPickerScreen.swift | 10 +- .../SelectivePrivacySettingsController.swift | 8 +- .../Themes/CustomWallpaperPicker.swift | 29 +-- .../Themes/ThemeColorsGridController.swift | 36 ++-- .../Sources/Themes/WallpaperGalleryItem.swift | 195 +++++++++++++++--- .../Themes/WallpaperOptionButtonNode.swift | 80 ++++++- .../MediaNavigationAccessoryHeaderNode.swift | 2 +- .../Sources/CallControllerNode.swift | 4 +- .../Sources/VoiceChatController.swift | 6 +- .../TelegramNotices/Sources/Notices.swift | 64 ++++++ .../Sources/ChatBotStartInputPanelNode.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 152 ++++++++------ .../Sources/ChatTextInputPanelNode.swift | 2 +- .../TelegramUI/Sources/ChatThemeScreen.swift | 24 ++- .../Sources/PeerInfo/PeerInfoScreen.swift | 2 +- .../TooltipUI/Sources/TooltipScreen.swift | 57 +++-- .../Sources/WallpaperBackgroundNode.swift | 26 ++- 21 files changed, 518 insertions(+), 194 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 5d55c6e9231..0f35cb28f3f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9149,7 +9149,7 @@ Sorry for the inconvenience."; "WallpaperPreview.ChatBottomText" = "Enjoy the view."; "Conversation.Theme.SetPhotoWallpaper" = "Choose Background from Photos"; -"Conversation.Theme.SetColorWallpaper" = "Choose Color as a Background"; +"Conversation.Theme.SetColorWallpaper" = "Set a Color as a Background"; "Conversation.Theme.OtherOptions" = "Other Options..."; "Conversation.Theme.ChooseWallpaperTitle" = "Choose Background"; @@ -9161,3 +9161,8 @@ Sorry for the inconvenience."; "WebApp.LaunchMoreInfo" = "More about this bot"; "WebApp.LaunchConfirmation" = "To launch this web app, you will connect to its website."; + +"WallpaperPreview.PreviewInNightMode" = "Preview this background in night mode."; +"WallpaperPreview.PreviewInDayMode" = "Preview this background in day mode."; + +"Conversation.Theme.ApplyBackground" = "Set as Background"; diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index a4f1b421d6d..c293f8802ca 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -1370,7 +1370,7 @@ public final class CalendarMessageScreen: ViewController { if self.selectionState?.dayRange == nil { if let selectionToolbarNode = self.selectionToolbarNode { let toolbarFrame = selectionToolbarNode.view.convert(selectionToolbarNode.bounds, to: self.view) - self.controller?.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip, style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in + self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip, style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in return .dismiss(consume: false) }), in: .current) } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 33c098db5c3..5bba1511fe0 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2079,7 +2079,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 8.0), size: CGSize()) - parentController.present(TooltipScreen(account: strongSelf.context.account, text: text, icon: .chatListPress, location: .point(location, .bottom), shouldDismissOnTouch: { point in + parentController.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: .chatListPress, location: .point(location, .bottom), shouldDismissOnTouch: { point in guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else { return .dismiss(consume: false) } diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index 879371f8bc8..2a70da2a89d 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -829,7 +829,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan text = strongSelf.presentationData.strings.Location_ProximityTip(EnginePeer(peer).compactDisplayTitle).string } - strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, text: text, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in + strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) }) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index c06f8529604..304251b50f5 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2010,18 +2010,18 @@ public func wallpaperMediaPickerController( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, - canDelete: Bool, - completion: @escaping (PHAsset) -> Void = { _ in } + completion: @escaping (PHAsset) -> Void = { _ in }, + openColors: @escaping () -> Void ) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil }) controller.requestController = { [weak controller] _, present in let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let mediaPickerController = MediaPickerScreen(context: context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper), mainButtonState: canDelete ? AttachmentMainButtonState(text: presentationData.strings.Conversation_Theme_ResetWallpaper, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true) : nil, mainButtonAction: canDelete ? { - let _ = context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: nil).start() + let mediaPickerController = MediaPickerScreen(context: context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper), mainButtonState: AttachmentMainButtonState(text: presentationData.strings.Conversation_Theme_SetColorWallpaper, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true), mainButtonAction: { controller?.dismiss(animated: true) - } : nil) + openColors() + }) mediaPickerController.customSelection = completion present(mediaPickerController, mediaPickerController.mediaPickerContext) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 1e06aaf916d..b5a667f6c0a 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -669,12 +669,8 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody)) entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts)) - switch kind { - case .presence, .voiceCalls, .forwards, .phoneNumber, .voiceMessages, .profilePhoto: - entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody)) - case .groupInvitations: - break - } + entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody)) + let phoneLink = "https://t.me/+\(phoneNumber)" if let settingInfoText = settingInfoText { entries.append(.settingInfo(presentationData.theme, settingInfoText, phoneLink)) diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index 2edaaea2d2b..454225d3d7d 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -196,7 +196,7 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE }).start() } -public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) { +public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) { let imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): @@ -279,24 +279,6 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa croppedImage = blurredImage(croppedImage, radius: 30.0)! } - if let brightness, abs(brightness) > 0.01 { - if let updatedImage = generateImage(croppedImage.size, contextGenerator: { size, context in - let bounds = CGRect(origin: .zero, size: size) - if let cgImage = croppedImage.cgImage { - context.draw(cgImage, in: bounds) - } - if brightness > 0.0 { - context.setFillColor(UIColor(rgb: 0xffffff, alpha: brightness).cgColor) - context.setBlendMode(.overlay) - } else { - context.setFillColor(UIColor(rgb: 0x000000, alpha: brightness * -1.0).cgColor) - } - context.fill(bounds) - }) { - croppedImage = updatedImage - } - } - let thumbnailDimensions = finalCropRect.size.fitted(CGSize(width: 320.0, height: 320.0)) let thumbnailImage = generateScaledImage(image: croppedImage, size: thumbnailDimensions, scale: 1.0) @@ -309,7 +291,12 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: nil) + var intensity: Int32? + if let brightness { + intensity = Int32(brightness * 100.0) + } + + 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 @@ -326,7 +313,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa completion() } - let _ = uploadWallpaper(account: context.account, resource: resource, settings: WallpaperSettings(blur: false, motion: mode.contains(.motion), colors: [], intensity: nil), forChat: true).start(next: { status in + 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) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift index 90848d1c201..963e1244c24 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift @@ -11,8 +11,8 @@ import TelegramUIPreferences import AccountContext import AttachmentUI -private func availableGradients(theme: PresentationTheme) -> [[UInt32]] { - if theme.overallDarkAppearance { +private func availableGradients(dark: Bool) -> [[UInt32]] { + if dark { return [ [0x1e3557, 0x151a36, 0x1c4352, 0x2a4541] as [UInt32], [0x1d223f, 0x1d1832, 0x1b2943, 0x141631] as [UInt32], @@ -39,8 +39,8 @@ private func availableGradients(theme: PresentationTheme) -> [[UInt32]] { } } -private func availableColors(theme: PresentationTheme) -> [UInt32] { - if theme.overallDarkAppearance { +private func availableColors(dark: Bool) -> [UInt32] { + if dark { return [ 0x1D2D3C, 0x111B26, @@ -150,6 +150,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina var pushController: (ViewController) -> Void = { _ in } var dismissControllers: (() -> Void)? + var openGallery: (() -> Void)? public init(context: AccountContext, mode: Mode = .default, canDelete: Bool = false) { self.context = context @@ -192,9 +193,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina self?.push(controller) } - if canDelete { - self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: self.presentationData.strings.Conversation_Theme_ResetWallpaper, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true))) - } + self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true))) } required public init(coder aDecoder: NSCoder) { @@ -270,7 +269,11 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina } public override func loadDisplayNode() { - self.displayNode = ThemeColorsGridControllerNode(context: self.context, presentationData: self.presentationData, controller: self, gradients: availableGradients(theme: self.presentationData.theme), colors: availableColors(theme: self.presentationData.theme), push: { [weak self] controller in + var dark = false + if case .default = self.mode { + dark = self.presentationData.theme.overallDarkAppearance + } + self.displayNode = ThemeColorsGridControllerNode(context: self.context, presentationData: self.presentationData, controller: self, gradients: availableGradients(dark: dark), colors: availableColors(dark: dark), push: { [weak self] controller in self?.pushController(controller) }, pop: { [weak self] in if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { @@ -327,12 +330,8 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina } @objc fileprivate func mainButtonPressed() { - guard case let .peer(peer) = self.mode else { - return - } - - let _ = self.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: nil).start() self.dismiss(animated: true) + self.openGallery?() } public var requestAttachmentMenuExpansion: () -> Void = {} @@ -385,18 +384,25 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext { } -public func standaloneColorPickerController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, canDelete: Bool, push: @escaping (ViewController) -> Void) -> ViewController { +public func standaloneColorPickerController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peer: EnginePeer, + push: @escaping (ViewController) -> Void, + openGallery: @escaping () -> Void +) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil }) controller.requestController = { _, present in - let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer), canDelete: canDelete) + let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer)) colorPickerController.pushController = { controller in push(controller) } colorPickerController.dismissControllers = { [weak controller] in controller?.dismiss(animated: true) } + colorPickerController.openGallery = openGallery present(colorPickerController, colorPickerController.mediaPickerContext) } return controller diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 6d68563aca5..378b2b380a8 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -19,6 +19,8 @@ import WallpaperResources import AppBundle import WallpaperBackgroundNode import TextFormat +import TooltipUI +import TelegramNotices struct WallpaperGalleryItemArguments { let colorPreview: Bool @@ -83,7 +85,7 @@ private func reference(for resource: MediaResource, media: Media, message: Messa final class WallpaperGalleryItemNode: GalleryItemNode { private let context: AccountContext - private let presentationData: PresentationData + private var presentationData: PresentationData var entry: WallpaperGalleryEntry? var source: WallpaperListSource? @@ -102,6 +104,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private let cancelButtonNode: WallpaperNavigationButtonNode private let shareButtonNode: WallpaperNavigationButtonNode + private let dayNightButtonNode: WallpaperNavigationButtonNode private let blurButtonNode: WallpaperOptionButtonNode private let motionButtonNode: WallpaperOptionButtonNode @@ -139,10 +142,16 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private var isReadyDisposable: Disposable? + private var isDarkAppearance: Bool = false + private var didChangeAppearance: Bool = false + private var darkAppearanceIntensity: CGFloat = 0.8 + init(context: AccountContext) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.isDarkAppearance = self.presentationData.theme.overallDarkAppearance + self.wrapperNode = ASDisplayNode() self.imageNode = TransformImageNode() self.imageNode.contentAnimations = .subsequentUpdates @@ -154,6 +163,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.blurredNode = BlurredImageNode() self.brightnessNode = ASDisplayNode() + self.brightnessNode.alpha = 0.0 self.messagesContainerNode = ASDisplayNode() self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) @@ -169,16 +179,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.serviceBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.33)) var sliderValueChangedImpl: ((CGFloat) -> Void)? - self.sliderNode = WallpaperSliderNode(minValue: 0.0, maxValue: 1.0, value: 0.5, valueChanged: { value, _ in + self.sliderNode = WallpaperSliderNode(minValue: 0.0, maxValue: 1.0, value: 0.7, valueChanged: { value, _ in sliderValueChangedImpl?(value) }) self.colorsButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_WallpaperColors, value: .colors(false, [.clear])) - self.cancelButtonNode = WallpaperNavigationButtonNode(content: .text(self.presentationData.strings.Common_Cancel), dark: false) + self.cancelButtonNode = WallpaperNavigationButtonNode(content: .text(self.presentationData.strings.Common_Cancel), dark: true) self.cancelButtonNode.enableSaturation = true - self.shareButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Chat/Links/Share"), size: CGSize(width: 28.0, height: 28.0)), dark: false) + self.shareButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Chat/Links/Share"), size: CGSize(width: 28.0, height: 28.0)), dark: true) self.shareButtonNode.enableSaturation = true + self.dayNightButtonNode = WallpaperNavigationButtonNode(content: .dayNight(isNight: self.isDarkAppearance), dark: true) + self.dayNightButtonNode.enableSaturation = true self.playButtonPlayImage = generateImage(CGSize(width: 48.0, height: 48.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -242,6 +254,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.addSubnode(self.sliderNode) self.addSubnode(self.cancelButtonNode) self.addSubnode(self.shareButtonNode) + self.addSubnode(self.dayNightButtonNode) self.imageNode.addSubnode(self.brightnessNode) @@ -252,22 +265,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.playButtonNode.addTarget(self, action: #selector(self.togglePlay), forControlEvents: .touchUpInside) self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) self.shareButtonNode.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside) + self.dayNightButtonNode.addTarget(self, action: #selector(self.dayNightPressed), forControlEvents: .touchUpInside) sliderValueChangedImpl = { [weak self] value in if let self { - let value = (value - 0.5) * 2.0 - if value < 0.0 { - self.brightnessNode.backgroundColor = UIColor(rgb: 0x000000) - self.brightnessNode.layer.compositingFilter = nil - self.brightnessNode.alpha = value * -1.0 - } else if value > 0.0 { - self.brightnessNode.backgroundColor = UIColor(rgb: 0xffffff) - self.brightnessNode.layer.compositingFilter = "overlayBlendMode" - self.brightnessNode.alpha = value - } else { - self.brightnessNode.layer.compositingFilter = nil - self.brightnessNode.alpha = 0.0 - } + self.updateIntensity(transition: .immediate) } } } @@ -297,7 +299,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } switch entry { case .asset, .contextResult: - return (self.sliderNode.value - 0.5) * 2.0 + return self.sliderNode.value default: return nil } @@ -311,6 +313,53 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.action?() } + + @objc private func dayNightPressed() { + self.isDarkAppearance = !self.isDarkAppearance + self.dayNightButtonNode.setIsNight(self.isDarkAppearance) + + if let layout = self.validLayout?.0 { + let offset = CGPoint(x: self.validOffset ?? 0.0, y: 0.0) + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + self.updateButtonsLayout(layout: layout, offset: offset, transition: transition) + self.updateMessagesLayout(layout: layout, offset: offset, transition: transition) + + if !self.didChangeAppearance { + self.didChangeAppearance = true + self.animateIntensityChange(delay: 0.15) + } else { + self.updateIntensity(transition: .animated(duration: 0.3, curve: .easeInOut)) + } + } + } + + private func animateIntensityChange(delay: Double) { + let targetValue: CGFloat = self.sliderNode.value + self.sliderNode.internalUpdateLayout(size: self.sliderNode.frame.size, value: 1.0) + self.sliderNode.ignoreUpdates = true + Queue.mainQueue().after(delay, { + self.brightnessNode.backgroundColor = UIColor(rgb: 0x000000) + self.brightnessNode.layer.compositingFilter = nil + + self.sliderNode.ignoreUpdates = false + let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .easeInOut) + self.sliderNode.animateValue(from: 1.0, to: targetValue, transition: transition) + self.updateIntensity(transition: transition) + }) + } + + private func updateIntensity(transition: ContainedViewLayoutTransition) { + let value = self.isDarkAppearance ? self.sliderNode.value : 1.0 + if value < 1.0 { + self.brightnessNode.backgroundColor = UIColor(rgb: 0x000000) + self.brightnessNode.layer.compositingFilter = nil + transition.updateAlpha(node: self.brightnessNode, alpha: 1.0 - value) + } else { + self.brightnessNode.layer.compositingFilter = nil + transition.updateAlpha(node: self.brightnessNode, alpha: 0.0) + } + } + @objc private func cancelPressed() { self.dismiss() } @@ -329,6 +378,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } + var showPreviewTooltip = false + if self.entry != entry || self.arguments.colorPreview != previousArguments.colorPreview { let previousEntry = self.entry self.entry = entry @@ -357,8 +408,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode { var isBlurrable = true - self.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) - switch entry { case let .wallpaper(wallpaper, _): self.nativeNode.update(wallpaper: wallpaper) @@ -393,6 +442,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } case .asset: self.nativeNode._internalUpdateIsSettingUpWallpaper() + //self.nativeNode.update(wallpaper: self.presentationData.chatWallpaper) self.nativeNode.isHidden = true self.patternButtonNode.isSelected = false self.playButtonNode.setIcon(self.playButtonRotateImage) @@ -402,7 +452,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.playButtonNode.setIcon(self.playButtonRotateImage) } - var isEditable = false + self.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) + var canShare = false switch entry { case let .wallpaper(wallpaper, message): @@ -566,7 +617,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } self.cropNode.removeFromSupernode() case let .asset(asset): - isEditable = true let dimensions = CGSize(width: asset.pixelWidth, height: asset.pixelHeight) contentSize = dimensions displaySize = dimensions.dividedByScreenScale().integralFloor @@ -576,8 +626,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { subtitleSignal = .single(nil) colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3)) self.wrapperNode.addSubnode(self.cropNode) + showPreviewTooltip = true case let .contextResult(result): - isEditable = true var imageDimensions: CGSize? var imageResource: TelegramMediaResource? var thumbnailDimensions: CGSize? @@ -631,11 +681,12 @@ final class WallpaperGalleryItemNode: GalleryItemNode { colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3)) subtitleSignal = .single(nil) self.wrapperNode.addSubnode(self.cropNode) + showPreviewTooltip = true } self.contentSize = contentSize - self.cancelButtonNode.dark = !isEditable - self.shareButtonNode.dark = !isEditable + //self.cancelButtonNode.dark = !isEditable + //self.shareButtonNode.dark = !isEditable self.shareButtonNode.isHidden = !canShare if self.cropNode.supernode == nil { @@ -713,6 +764,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.updateButtonsLayout(layout: layout, offset: CGPoint(), transition: .immediate) self.updateMessagesLayout(layout: layout, offset: CGPoint(), transition: .immediate) } + + if showPreviewTooltip { + Queue.mainQueue().after(0.35) { + self.maybePresentPreviewTooltip() + } + if self.isDarkAppearance && !self.didChangeAppearance { + Queue.mainQueue().justDispatch { + self.didChangeAppearance = true + self.animateIntensityChange(delay: 0.35) + } + } + } } override func screenFrameUpdated(_ frame: CGRect) { @@ -966,7 +1029,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { if let source = self.source { switch source { case .asset, .contextResult: - additionalYOffset -= 44.0 + if self.isDarkAppearance { + additionalYOffset -= 44.0 + } default: break } @@ -996,8 +1061,17 @@ final class WallpaperGalleryItemNode: GalleryItemNode { var playAlpha: CGFloat = 0.0 let sliderSize = CGSize(width: 268.0, height: 30.0) - let sliderFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - sliderSize.width) / 2.0) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 52.0 + offset.y), size: sliderSize) - var sliderIsHidden = true + var sliderFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - sliderSize.width) / 2.0) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 52.0 + offset.y), size: sliderSize) + var sliderAlpha: CGFloat = 0.0 + var sliderScale: CGFloat = 0.2 + if !additionalYOffset.isZero { + sliderAlpha = 1.0 + sliderScale = 1.0 + } else { + sliderFrame = sliderFrame.offsetBy(dx: 0.0, dy: 22.0) + } + + var dayNightHidden = true let cancelSize = self.cancelButtonNode.measure(layout.size) let cancelFrame = CGRect(origin: CGPoint(x: 16.0 + offset.x, y: 16.0), size: cancelSize) @@ -1013,13 +1087,13 @@ final class WallpaperGalleryItemNode: GalleryItemNode { blurFrame = leftButtonFrame motionAlpha = 1.0 motionFrame = rightButtonFrame - sliderIsHidden = false + dayNightHidden = false case .contextResult: blurAlpha = 1.0 blurFrame = leftButtonFrame motionAlpha = 1.0 motionFrame = rightButtonFrame - sliderIsHidden = false + dayNightHidden = false case let .wallpaper(wallpaper, _): switch wallpaper { case .builtin: @@ -1104,12 +1178,16 @@ final class WallpaperGalleryItemNode: GalleryItemNode { transition.updateAlpha(node: self.playButtonNode, alpha: playAlpha * alpha) transition.updateSublayerTransformScale(node: self.playButtonNode, scale: max(0.1, playAlpha)) - transition.updateFrame(node: self.sliderNode, frame: sliderFrame) + transition.updateFrameAsPositionAndBounds(node: self.sliderNode, frame: sliderFrame) + transition.updateAlpha(node: self.sliderNode, alpha: sliderAlpha * alpha) + transition.updateTransformScale(node: self.sliderNode, scale: sliderScale) self.sliderNode.updateLayout(size: sliderFrame.size) - self.sliderNode.isHidden = sliderIsHidden transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame) transition.updateFrame(node: self.shareButtonNode, frame: shareFrame) + transition.updateFrame(node: self.dayNightButtonNode, frame: shareFrame) + + self.dayNightButtonNode.isHidden = dayNightHidden } private func updateMessagesLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) { @@ -1182,7 +1260,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { case .asset, .contextResult: topMessageText = presentationData.strings.WallpaperPreview_CropTopText bottomMessageText = presentationData.strings.WallpaperPreview_CropBottomText - bottomInset += 44.0 + if self.isDarkAppearance { + bottomInset += 44.0 + } case .customColor: topMessageText = presentationData.strings.WallpaperPreview_CustomColorTopText bottomMessageText = presentationData.strings.WallpaperPreview_CustomColorBottomText @@ -1197,7 +1277,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } - let theme = self.presentationData.theme.withUpdated(preview: true) + let theme = self.presentationData.theme.withUpdated(preview: false) let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, isCentered: false)) @@ -1302,6 +1382,16 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.cropNode.zoom(to: CGRect(x: (contentSize.width - fittedSize.width) / 2.0, y: (contentSize.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height)) } self.blurredNode.frame = self.imageNode.bounds + + let displayMode: WallpaperDisplayMode + if case .regular = layout.metrics.widthClass { + displayMode = .aspectFit + } else { + displayMode = .aspectFill + } + + self.nativeNode.frame = self.wrapperNode.bounds + self.nativeNode.updateLayout(size: self.nativeNode.bounds.size, displayMode: displayMode, transition: .immediate) } self.brightnessNode.frame = self.imageNode.bounds @@ -1322,4 +1412,41 @@ final class WallpaperGalleryItemNode: GalleryItemNode { func animateWallpaperAppeared() { self.nativeNode.animateEvent(transition: .animated(duration: 2.0, curve: .spring), extendAnimation: true) } + + private var displayedPreviewTooltip = false + private func maybePresentPreviewTooltip() { + guard !self.displayedPreviewTooltip else { + return + } + + let frame = self.dayNightButtonNode.view.convert(self.dayNightButtonNode.bounds, to: self.view) + let currentTimestamp = Int32(Date().timeIntervalSince1970) + + let isDark = self.isDarkAppearance + + let signal: Signal<(Int32, Int32), NoError> + if isDark { + signal = ApplicationSpecificNotice.getChatWallpaperLightPreviewTip(accountManager: self.context.sharedContext.accountManager) + } else { + signal = ApplicationSpecificNotice.getChatWallpaperDarkPreviewTip(accountManager: self.context.sharedContext.accountManager) + } + + let _ = (signal + |> deliverOnMainQueue).start(next: { [weak self] count, timestamp in + if let strongSelf = self, (count < 2 && currentTimestamp > timestamp + 24 * 60 * 60) || "".isEmpty { + 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 + return .dismiss(consume: false) + }) + strongSelf.galleryController()?.present(controller, in: .current) + + if isDark { + let _ = ApplicationSpecificNotice.incrementChatWallpaperLightPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() + } else { + let _ = ApplicationSpecificNotice.incrementChatWallpaperDarkPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() + } + } + }) + } } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 5e328f8604e..300b6d147d6 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit import SwiftSignalKit import Postbox import CheckNode +import AnimationUI enum WallpaperOptionButtonValue { case check(Bool) @@ -93,6 +94,7 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { enum Content { case icon(image: UIImage?, size: CGSize) case text(String) + case dayNight(isNight: Bool) } var enableSaturation: Bool = false @@ -115,6 +117,7 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { private var backgroundNode: ASDisplayNode private let iconNode: ASImageNode private let textNode: ImmediateTextNode + private var animationNode: AnimationNode? func setIcon(_ image: UIImage?) { self.iconNode.image = generateTintedImage(image: image, color: .white) @@ -141,6 +144,12 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { case let .icon(icon, _): title = "" self.iconNode.image = generateTintedImage(image: icon, color: .white) + case let .dayNight(isNight): + title = "" + let animationNode = AnimationNode(animation: isNight ? "anim_sun_reverse" : "anim_sun", colors: [:], scale: 1.0) + animationNode.speed = 1.5 + animationNode.isUserInteractionEnabled = false + self.animationNode = animationNode } self.textNode = ImmediateTextNode() @@ -152,19 +161,53 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { self.addSubnode(self.iconNode) self.addSubnode(self.textNode) + if let animationNode = self.animationNode { + self.addSubnode(animationNode) + } + self.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") strongSelf.backgroundNode.alpha = 0.4 + + strongSelf.iconNode.layer.removeAnimation(forKey: "opacity") + strongSelf.iconNode.alpha = 0.4 + + strongSelf.textNode.layer.removeAnimation(forKey: "opacity") + strongSelf.textNode.alpha = 0.4 + +// if let animationNode = strongSelf.animationNode { +// animationNode.layer.removeAnimation(forKey: "opacity") +// animationNode.alpha = 0.4 +// } } else { strongSelf.backgroundNode.alpha = 1.0 strongSelf.backgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + + strongSelf.iconNode.alpha = 1.0 + strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + + strongSelf.textNode.alpha = 1.0 + strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + +// if let animationNode = strongSelf.animationNode { +// animationNode.alpha = 1.0 +// animationNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) +// } } } } } + func setIsNight(_ isNight: Bool) { + self.animationNode?.setAnimation(name: !isNight ? "anim_sun_reverse" : "anim_sun", colors: [:]) + self.animationNode?.speed = 1.5 + Queue.mainQueue().after(0.01) { + self.animationNode?.playOnce() + } + } + var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { didSet { } @@ -179,6 +222,8 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { return CGSize(width: ceil(size.width) + 16.0, height: 28.0) case let .icon(_, size): return size + case .dayNight: + return CGSize(width: 28.0, height: 28.0) } } @@ -199,6 +244,11 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { if let textSize = self.textSize { self.textNode.frame = CGRect(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0), width: textSize.width, height: textSize.height) } + + if let animationNode = self.animationNode { + animationNode.bounds = CGRect(origin: .zero, size: CGSize(width: 24.0, height: 24.0)) + animationNode.position = CGPoint(x: 14.0, y: 14.0) + } } } @@ -491,7 +541,16 @@ final class WallpaperSliderNode: ASDisplayNode { self.view.addGestureRecognizer(tapGestureRecognizer) } - func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + var ignoreUpdates = false + func animateValue(from: CGFloat, to: CGFloat, transition: ContainedViewLayoutTransition = .immediate) { + guard let size = self.validLayout else { + return + } + self.internalUpdateLayout(size: size, value: from) + self.internalUpdateLayout(size: size, value: to, transition: transition) + } + + func internalUpdateLayout(size: CGSize, value: CGFloat, transition: ContainedViewLayoutTransition = .immediate) { self.validLayout = size transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: size)) @@ -506,10 +565,17 @@ final class WallpaperSliderNode: ASDisplayNode { } let range = self.maxValue - self.minValue - let value = (self.value - self.minValue) / range + let value = (value - self.minValue) / range let foregroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: value * size.width, height: size.height)) - transition.updateFrameAdditive(node: self.foregroundNode, frame: foregroundFrame) - transition.updateFrameAdditive(node: self.foregroundLightNode, frame: foregroundFrame) + transition.updateFrame(node: self.foregroundNode, frame: foregroundFrame) + transition.updateFrame(node: self.foregroundLightNode, frame: foregroundFrame) + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + guard !self.ignoreUpdates else { + return + } + self.internalUpdateLayout(size: size, value: self.value, transition: transition) } @objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) { @@ -525,14 +591,10 @@ final class WallpaperSliderNode: ASDisplayNode { self.value = max(self.minValue, min(self.maxValue, self.value + delta)) gestureRecognizer.setTranslation(CGPoint(), in: gestureRecognizer.view) - if self.value == 2.0 && previousValue != 2.0 { + if self.value == 0.0 && previousValue != 0.0 { self.hapticFeedback.impact(.soft) } else if self.value == 1.0 && previousValue != 1.0 { self.hapticFeedback.impact(.soft) - } else if self.value == 2.5 && previousValue != 2.5 { - self.hapticFeedback.impact(.soft) - } else if self.value == 0.05 && previousValue != 0.05 { - self.hapticFeedback.impact(.soft) } if abs(previousValue - self.value) >= 0.001 { self.valueChanged(self.value, false) diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift index 87222f20e4a..de6f7c79659 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -526,7 +526,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi let _ = (ApplicationSpecificNotice.incrementAudioRateOptionsTip(accountManager: self.context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { [weak self] value in if let strongSelf = self, let controller = strongSelf.getController?(), value == 2 { - let tooltipController = TooltipScreen(account: strongSelf.context.account, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + let tooltipController = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in return .dismiss(consume: false) }) controller.present(tooltipController, in: .window(.root)) diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift index f1d8d74b9c8..25cc58eee20 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift @@ -760,8 +760,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro }) else { return } - - self.present?(TooltipScreen(account: self.account, text: self.presentationData.strings.Call_CameraOrScreenTooltip, style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in + + self.present?(TooltipScreen(account: self.account, sharedContext: self.sharedContext, text: self.presentationData.strings.Call_CameraOrScreenTooltip, style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index ccd9a5bfeea..b6020e48de2 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -2310,7 +2310,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController } else { text = presentationData.strings.VoiceChat_RecordingInProgress } - strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, text: text, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in + strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in return .dismiss(consume: true) }), in: .window(.root)) } @@ -3507,7 +3507,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController if !callState.subscribedToScheduled { let location = self.actionButton.view.convert(self.actionButton.bounds, to: self.view).center let point = CGRect(origin: CGPoint(x: location.x - 5.0, y: location.y - 5.0 - 68.0), size: CGSize(width: 10.0, height: 10.0)) - self.controller?.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.VoiceChat_ReminderNotify, style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in + self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_ReminderNotify, style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) }), in: .window(.root)) } @@ -6411,7 +6411,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController point.origin.y += 32.0 } } - self.controller?.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.VoiceChat_UnmuteSuggestion, style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in + self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_UnmuteSuggestion, style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) }), in: .window(.root)) } diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index b5e2bb12943..8995477dff6 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -170,6 +170,8 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case audioRateOptionsTip = 36 case translationSuggestion = 37 case sendWhenOnlineTip = 38 + case chatWallpaperLightPreviewTip = 39 + case chatWallpaperDarkPreviewTip = 40 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -332,6 +334,14 @@ private struct ApplicationSpecificNoticeKeys { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemeDarkPreviewTip.key) } + static func chatWallpaperLightPreviewTip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperLightPreviewTip.key) + } + + static func chatWallpaperDarkPreviewTip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperDarkPreviewTip.key) + } + static func chatForwardOptionsTip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatForwardOptionsTip.key) } @@ -1109,6 +1119,60 @@ public struct ApplicationSpecificNotice { } } + public static func getChatWallpaperLightPreviewTip(accountManager: AccountManager) -> Signal<(Int32, Int32), NoError> { + return accountManager.transaction { transaction -> (Int32, Int32) in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) { + return (value.counter, value.timestamp) + } else { + return (0, 0) + } + } + } + + public static func incrementChatWallpaperLightPreviewTip(accountManager: AccountManager, count: Int = 1, timestamp: Int32) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) { + currentValue = value.counter + } + let previousValue = currentValue + currentValue += Int32(count) + + if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip(), entry) + } + + return Int(previousValue) + } + } + + public static func getChatWallpaperDarkPreviewTip(accountManager: AccountManager) -> Signal<(Int32, Int32), NoError> { + return accountManager.transaction { transaction -> (Int32, Int32) in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) { + return (value.counter, value.timestamp) + } else { + return (0, 0) + } + } + } + + public static func incrementChatWallpaperDarkPreviewTip(accountManager: AccountManager, count: Int = 1, timestamp: Int32) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) { + currentValue = value.counter + } + let previousValue = currentValue + currentValue += Int32(count) + + if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip(), entry) + } + + return Int(previousValue) + } + } + public static func getChatForwardOptionsTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatForwardOptionsTip())?.get(ApplicationSpecificCounterNotice.self) { diff --git a/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift index e0989b05dc1..79689abe9ff 100644 --- a/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift @@ -130,7 +130,7 @@ final class ChatBotStartInputPanelNode: ChatInputPanelNode { tooltipController.location = .point(location, .bottom) } } else { - let controller = TooltipScreen(account: context.account, text: self.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in + let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: self.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in return .ignore }) controller.alwaysVisible = true diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index bd91ce4895d..5b5e2c9c82f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -676,14 +676,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } - if strongSelf.presentVoiceMessageDiscardAlert(action: { [weak self] in - if let strongSelf = self { - Queue.mainQueue().after(0.1, { - let _ = strongSelf.controllerInteraction?.openMessage(message, mode) - }) + let displayVoiceMessageDiscardAlert: () -> Bool = { + if strongSelf.presentVoiceMessageDiscardAlert(action: { [weak self] in + if let strongSelf = self { + Queue.mainQueue().after(0.1, { + let _ = strongSelf.controllerInteraction?.openMessage(message, mode) + }) + } + }, performAction: false) { + return false } - }, performAction: false) { - return false + return true } strongSelf.commitPurposefulAction() @@ -696,6 +699,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G for media in message.media { if media is TelegramMediaMap { + if !displayVoiceMessageDiscardAlert() { + return false + } isLocation = true } if let file = media as? TelegramMediaFile, file.isInstantVideo { @@ -707,12 +713,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia { switch extendedMedia { case .preview: - strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id) - return true + if displayVoiceMessageDiscardAlert() { + strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id) + return true + } else { + return false + } case .full: break } } else if let action = media as? TelegramMediaAction { + if !displayVoiceMessageDiscardAlert() { + return false + } switch action.action { case .pinnedMessageUpdated: for attribute in message.attributes { @@ -5839,6 +5852,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { let (themeEmoticonPreview, darkAppearancePreview) = themeEmoticonAndDarkAppearance + var chatWallpaper = chatWallpaper + let previousTheme = strongSelf.presentationData.theme let previousStrings = strongSelf.presentationData.strings let previousChatWallpaper = strongSelf.presentationData.chatWallpaper @@ -5846,7 +5861,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var themeEmoticon = themeEmoticon if let themeEmoticonPreview = themeEmoticonPreview { if !themeEmoticonPreview.isEmpty { - themeEmoticon = themeEmoticonPreview + if themeEmoticon != themeEmoticonPreview { + chatWallpaper = nil + themeEmoticon = themeEmoticonPreview + } } else { themeEmoticon = nil } @@ -5857,7 +5875,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var presentationData = presentationData var useDarkAppearance = presentationData.theme.overallDarkAppearance - + if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon?.strippedEmoji == themeEmoticon.strippedEmoji }) { if let darkAppearancePreview = darkAppearancePreview { useDarkAppearance = darkAppearancePreview @@ -5896,7 +5914,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G lightWallpaper = theme.chat.defaultWallpaper } - lightTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme + var preferredBaseTheme: TelegramBaseTheme? + if let baseTheme = themeSettings.themePreferredBaseTheme[themeSettings.theme.index], [.classic, .day].contains(baseTheme) { + preferredBaseTheme = baseTheme + } + + lightTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, baseTheme: preferredBaseTheme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme } else { lightTheme = presentationData.theme lightWallpaper = presentationData.chatWallpaper @@ -5905,7 +5928,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index] let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index]) - darkTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: automaticTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme + var preferredBaseTheme: TelegramBaseTheme? + if let baseTheme = themeSettings.themePreferredBaseTheme[automaticTheme.index], [.night, .tinted].contains(baseTheme) { + preferredBaseTheme = baseTheme + } else { + preferredBaseTheme = .night + } + + darkTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: automaticTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme if let themeSpecificWallpaper = themeSpecificWallpaper { darkWallpaper = themeSpecificWallpaper @@ -5944,7 +5974,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G presentationData = presentationData.withUpdated(chatWallpaper: chatWallpaper) } - let isFirstTime = !strongSelf.didSetPresentationData strongSelf.presentationData = presentationData strongSelf.didSetPresentationData = true @@ -14314,7 +14343,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { @@ -14407,7 +14436,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { @@ -14521,7 +14550,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { @@ -18508,12 +18537,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let selectedEmoticon: String? = themeEmoticon + var canResetWallpaper = false + if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { + canResetWallpaper = cachedUserData.wallpaper != nil + } + let controller = ChatThemeScreen( context: context, updatedPresentationData: strongSelf.updatedPresentationData, animatedEmojiStickers: animatedEmojiStickers, initiallySelectedEmoticon: selectedEmoticon, peerName: strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "", + canResetWallpaper: canResetWallpaper, previewTheme: { [weak self] emoticon, dark in if let strongSelf = self { strongSelf.presentCrossfadeSnapshot() @@ -18540,54 +18575,47 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - var canDelete = false - if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { - canDelete = cachedUserData.wallpaper != nil - } - let controller = wallpaperMediaPickerController( - context: strongSelf.context, - updatedPresentationData: strongSelf.updatedPresentationData, - peer: EnginePeer(peer), - canDelete: canDelete, - completion: { asset in - guard let strongSelf = self else { - return - } - let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) - controller.navigationPresentation = .modal - controller.apply = { [weak self] wallpaper, options, cropRect, brightness in - if let strongSelf = self { - uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, peerId: peerId, completion: { - dismissControllers() - }) + var openWallpaperPickerImpl: (() -> Void)? + let openWallpaperPicker = { + let controller = wallpaperMediaPickerController( + context: strongSelf.context, + updatedPresentationData: strongSelf.updatedPresentationData, + peer: EnginePeer(peer), + completion: { asset in + guard let strongSelf = self else { + return } + let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) + controller.navigationPresentation = .modal + controller.apply = { [weak self] wallpaper, options, cropRect, brightness in + if let strongSelf = self { + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, peerId: peerId, completion: { + dismissControllers() + }) + } + } + strongSelf.push(controller) + }, + openColors: { [weak self] in + guard let strongSelf = self else { + return + } + let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), push: { [weak self] controller in + if let strongSelf = self { + strongSelf.push(controller) + } + }, openGallery: { + openWallpaperPickerImpl?() + }) + controller.navigationPresentation = .flatModal + strongSelf.push(controller) } - strongSelf.push(controller) - } - ) - controller.navigationPresentation = .flatModal - strongSelf.push(controller) - }, - changeColor: { - guard let strongSelf = self else { - return - } - if let themeController = strongSelf.themeScreen { - strongSelf.themeScreen = nil - themeController.dimTapped() - } - - var canDelete = false - if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { - canDelete = cachedUserData.wallpaper != nil + ) + controller.navigationPresentation = .flatModal + strongSelf.push(controller) } - let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), canDelete: canDelete, push: { [weak self] controller in - if let strongSelf = self { - strongSelf.push(controller) - } - }) - controller.navigationPresentation = .flatModal - strongSelf.push(controller) + openWallpaperPickerImpl = openWallpaperPicker + openWallpaperPicker() }, completion: { [weak self] emoticon in guard let strongSelf = self, let peerId else { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index a695150d1c7..aa7835459d7 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1483,7 +1483,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { tooltipController.location = .point(location, .bottom) } } else { - let controller = TooltipScreen(account: context.account, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in + let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in return .ignore }) controller.alwaysVisible = true diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index dd015c8b585..ae3a1955fb3 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -554,9 +554,9 @@ final class ChatThemeScreen: ViewController { private let animatedEmojiStickers: [String: [StickerPackItem]] private let initiallySelectedEmoticon: String? private let peerName: String + let canResetWallpaper: Bool private let previewTheme: (String?, Bool?) -> Void fileprivate let changeWallpaper: () -> Void - fileprivate let changeColor: () -> Void private let completion: (String?) -> Void private var presentationData: PresentationData @@ -578,9 +578,9 @@ final class ChatThemeScreen: ViewController { animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, peerName: String, + canResetWallpaper: Bool, previewTheme: @escaping (String?, Bool?) -> Void, changeWallpaper: @escaping () -> Void, - changeColor: @escaping () -> Void, completion: @escaping (String?) -> Void ) { self.context = context @@ -588,9 +588,9 @@ final class ChatThemeScreen: ViewController { self.animatedEmojiStickers = animatedEmojiStickers self.initiallySelectedEmoticon = initiallySelectedEmoticon self.peerName = peerName + self.canResetWallpaper = canResetWallpaper self.previewTheme = previewTheme self.changeWallpaper = changeWallpaper - self.changeColor = changeColor self.completion = completion super.init(navigationBarPresentationData: nil) @@ -1013,18 +1013,22 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } private func updateButtons() { + let canResetWallpaper = self.controller?.canResetWallpaper ?? false + let doneButtonTitle: String let otherButtonTitle: String var accentButtonTheme = true + var destructiveOtherButton = false if self.selectedEmoticon == self.initiallySelectedEmoticon { doneButtonTitle = self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper - otherButtonTitle = self.presentationData.strings.Conversation_Theme_SetColorWallpaper + otherButtonTitle = canResetWallpaper ? self.presentationData.strings.Conversation_Theme_ResetWallpaper : self.presentationData.strings.Common_Cancel accentButtonTheme = false + destructiveOtherButton = canResetWallpaper } else if self.selectedEmoticon == nil && self.initiallySelectedEmoticon != nil { doneButtonTitle = self.presentationData.strings.Conversation_Theme_Reset otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions } else { - doneButtonTitle = self.presentationData.strings.Conversation_Theme_Apply + doneButtonTitle = self.presentationData.strings.Conversation_Theme_ApplyBackground otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions } @@ -1040,7 +1044,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } self.doneButton.updateTheme(buttonTheme) - self.otherButton.setTitle(otherButtonTitle, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) + self.otherButton.setTitle(otherButtonTitle, with: Font.regular(17.0), with: destructiveOtherButton ? self.presentationData.theme.actionSheet.destructiveActionTextColor : self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) } private var switchThemeIconAnimator: DisplayLinkAnimator? @@ -1102,7 +1106,11 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega if self.selectedEmoticon != self.initiallySelectedEmoticon { self.setEmoticon(self.initiallySelectedEmoticon) } else { - self.controller?.changeColor() + if self.controller?.canResetWallpaper == true { + + } else { + self.cancelButtonPressed() + } } } @@ -1229,7 +1237,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega if let strongSelf = self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 { strongSelf.displayedPreviewTooltip = true - strongSelf.present?(TooltipScreen(account: strongSelf.context.account, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLight(strongSelf.peerName).string : strongSelf.presentationData.strings.Conversation_Theme_PreviewDark(strongSelf.peerName).string, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLight(strongSelf.peerName).string : strongSelf.presentationData.strings.Conversation_Theme_PreviewDark(strongSelf.peerName).string, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 3ae82617db9..afc7f249cf2 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -8467,7 +8467,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return } let buttonFrame = buttonNode.view.convert(buttonNode.bounds, to: self.view) - controller.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.SharedMedia_CalendarTooltip, style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in + controller.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.SharedMedia_CalendarTooltip, style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in return .dismiss(consume: false) }), in: .current) } diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index 0d0b017547e..36e6eaacc7c 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -11,6 +11,7 @@ import TelegramCore import TextFormat import Postbox import UrlEscaping +import AccountContext public protocol TooltipCustomContentNode: ASDisplayNode { func animateIn() @@ -127,12 +128,11 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private let backgroundContainerNode: ASDisplayNode private let backgroundClipNode: ASDisplayNode private let backgroundMaskNode: ASDisplayNode - private var effectView: UIView? + private var effectNode: NavigationBackgroundNode? private var gradientNode: ASDisplayNode? private var arrowGradientNode: ASDisplayNode? private let arrowNode: ASImageNode private let arrowContainer: ASDisplayNode - private var arrowEffectView: UIView? private let animatedStickerNode: AnimatedStickerNode private var downArrowsNode: DownArrowsIconNode? private let textNode: ImmediateTextNode @@ -143,7 +143,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private var validLayout: ContainerViewLayout? - init(account: Account, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) { + init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) { self.tooltipStyle = style self.icon = icon self.customContentNode = customContentNode @@ -210,9 +210,16 @@ private final class TooltipScreenNode: ViewControllerTracingNode { self.arrowContainer = ASDisplayNode() + let theme = sharedContext.currentPresentationData.with { $0 }.theme let fontSize: CGFloat if case .top = location { - self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + let backgroundColor: UIColor + if theme.overallDarkAppearance { + backgroundColor = theme.rootController.navigationBar.blurredBackgroundColor + } else { + backgroundColor = UIColor(rgb: 0x000000, alpha: 0.6) + } + self.effectNode = NavigationBackgroundNode(color: backgroundColor) self.backgroundMaskNode.addSubnode(self.backgroundClipNode) self.backgroundClipNode.clipsToBounds = true if case let .point(_, arrowPosition) = location, case .right = arrowPosition { @@ -258,20 +265,28 @@ private final class TooltipScreenNode: ViewControllerTracingNode { maskLayer.frame = CGRect(origin: CGPoint(), size: arrowSize) self.arrowContainer.layer.mask = maskLayer } else { - let effect: UIBlurEffect - if case .light = style { - effect = UIBlurEffect(style: .light) + var enableSaturation = true + let backgroundColor: UIColor + if case let .customBlur(color) = style { + backgroundColor = color + enableSaturation = false + } else if case .light = style { + backgroundColor = theme.rootController.navigationBar.blurredBackgroundColor } else { - effect = UIBlurEffect(style: .dark) + if theme.overallDarkAppearance { + backgroundColor = theme.rootController.navigationBar.blurredBackgroundColor + } else { + backgroundColor = UIColor(rgb: 0x000000, alpha: 0.6) + } } - self.effectView = UIVisualEffectView(effect: effect) + self.effectNode = NavigationBackgroundNode(color: backgroundColor, enableBlur: true, enableSaturation: enableSaturation) self.backgroundMaskNode.addSubnode(self.backgroundClipNode) self.backgroundClipNode.clipsToBounds = true if case let .point(_, arrowPosition) = location, case .right = arrowPosition { self.backgroundClipNode.cornerRadius = 8.5 } else { - self.backgroundClipNode.cornerRadius = 14.0 + self.backgroundClipNode.cornerRadius = 12.5 } if #available(iOS 13.0, *) { self.backgroundClipNode.layer.cornerCurve = .continuous @@ -318,8 +333,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode { if let gradientNode = self.gradientNode { self.backgroundContainerNode.addSubnode(gradientNode) self.containerNode.addSubnode(self.arrowContainer) - } else if let effectView = self.effectView { - self.backgroundContainerNode.view.addSubview(effectView) + } else if let effectNode = self.effectNode { + self.backgroundContainerNode.addSubnode(effectNode) self.backgroundContainerNode.layer.mask = self.backgroundMaskNode.layer } self.containerNode.addSubnode(self.textNode) @@ -430,7 +445,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let backgroundHeight: CGFloat switch self.tooltipStyle { - case .default, .gradient: + case .default, .gradient, .customBlur: backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 case .light: backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0) @@ -472,8 +487,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode { transition.updateFrame(node: self.backgroundMaskNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0)) transition.updateFrame(node: self.backgroundClipNode, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: backgroundFrame.size)) - if let effectView = self.effectView { - transition.updateFrame(view: effectView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0)) + if let effectNode = self.effectNode { + let effectFrame = CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0) + transition.updateFrame(node: effectNode, frame: effectFrame) + effectNode.update(size: effectFrame.size, transition: transition) } if let gradientNode = self.gradientNode { transition.updateFrame(node: gradientNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) @@ -501,7 +518,6 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let arrowBounds = CGRect(origin: CGPoint(), size: arrowSize) self.arrowNode.frame = arrowBounds - self.arrowEffectView?.frame = arrowBounds self.arrowGradientNode?.frame = CGRect(origin: CGPoint(x: -arrowFrame.minX + backgroundFrame.minX, y: 0.0), size: backgroundFrame.size) case .right: arrowFrame = CGRect(origin: CGPoint(x: backgroundFrame.width + arrowSize.height, y: rect.midY), size: CGSize(width: arrowSize.height, height: arrowSize.width)) @@ -512,12 +528,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let arrowBounds = CGRect(origin: .zero, size: arrowSize) self.arrowNode.frame = arrowBounds - self.arrowEffectView?.frame = arrowBounds self.arrowGradientNode?.frame = arrowBounds } } else { self.arrowNode.isHidden = true - self.arrowEffectView?.isHidden = true } transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize)) @@ -672,10 +686,12 @@ public final class TooltipScreen: ViewController { public enum Style { case `default` case light + case customBlur(UIColor) case gradient(UIColor, UIColor) } private let account: Account + private let sharedContext: SharedAccountContext public let text: String public let textEntities: [MessageTextEntity] private let style: TooltipScreen.Style @@ -707,8 +723,9 @@ public final class TooltipScreen: ViewController { public var alwaysVisible = false - public init(account: Account, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) { + public init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) { self.account = account + self.sharedContext = sharedContext self.text = text self.textEntities = textEntities self.style = style @@ -776,7 +793,7 @@ public final class TooltipScreen: ViewController { } override public func loadDisplayNode() { - self.displayNode = TooltipScreenNode(account: self.account, text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, inset: self.inset, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in + self.displayNode = TooltipScreenNode(account: self.account, sharedContext: self.sharedContext, text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, inset: self.inset, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 0095b1c4548..af8c9bc9f07 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -743,6 +743,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode private var gradientBackgroundNode: GradientBackgroundNode? private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode? private let patternImageLayer: EffectImageLayer + private let dimLayer: SimpleLayer private var isGeneratingPatternImage: Bool = false private let bakedBackgroundView: UIImageView @@ -862,6 +863,9 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.bakedBackgroundView = UIImageView() self.bakedBackgroundView.isHidden = true + self.dimLayer = SimpleLayer() + self.dimLayer.backgroundColor = UIColor.black.cgColor + super.init() if #available(iOS 12.0, *) { @@ -885,6 +889,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.contentNode.frame = self.bounds self.addSubnode(self.contentNode) self.layer.addSublayer(self.patternImageLayer) + + self.layer.addSublayer(self.dimLayer) } deinit { @@ -892,6 +898,19 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.wallpaperDisposable.dispose() self.imageDisposable.dispose() } + + private func updateDimming() { + guard let wallpaper = self.wallpaper, let theme = self.bubbleTheme else { + return + } + var dimAlpha: Float = 0.0 + if case let .file(file) = wallpaper, !file.isPattern { + if let intensity = file.settings.intensity, intensity < 100, theme.overallDarkAppearance == true { + dimAlpha = 1.0 - max(0.0, min(1.0, Float(intensity) / 100.0)) + } + } + self.dimLayer.opacity = dimAlpha + } func update(wallpaper: TelegramWallpaper) { if self.wallpaper == wallpaper { @@ -915,7 +934,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode gradientColors = file.settings.colors gradientAngle = file.settings.rotation ?? 0 } - + var scheduleLoopingEvent = false if gradientColors.count >= 3 { let mappedColors = gradientColors.map { color -> UIColor in @@ -1032,6 +1051,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.animateEvent(transition: .animated(duration: 0.7, curve: .linear), extendAnimation: false) } } + + self.updateDimming() } func _internalUpdateIsSettingUpWallpaper() { @@ -1304,6 +1325,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode transition.updateFrame(node: outgoingBackgroundNode, frame: CGRect(origin: CGPoint(), size: size)) outgoingBackgroundNode.update(rect: CGRect(origin: CGPoint(), size: size), within: size, transition: transition) } + + transition.updateFrame(layer: self.dimLayer, frame: CGRect(origin: CGPoint(), size: size)) self.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: transition) @@ -1378,6 +1401,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } self.updateBubbles() + self.updateDimming() } } From be2f1adb5084b6326016e2ef900f5568221f0729 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 9 Apr 2023 02:32:03 +0400 Subject: [PATCH 17/57] Chat wallpaper improvements --- .../Sources/MediaPickerScreen.swift | 53 ++++++++++++------- .../Themes/CustomWallpaperPicker.swift | 2 +- .../ThemeAccentColorControllerNode.swift | 4 ++ .../Themes/ThemeColorsGridController.swift | 1 + .../Sources/Themes/WallpaperGalleryItem.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 14 ++++- .../TelegramUI/Sources/ChatThemeScreen.swift | 8 ++- 7 files changed, 60 insertions(+), 24 deletions(-) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 304251b50f5..97c3a39a9de 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -385,32 +385,40 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { override func didLoad() { super.didLoad() + guard let controller = self.controller else { + return + } + self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollView.showsVerticalScrollIndicator = false - let selectionGesture = MediaPickerGridSelectionGesture() - selectionGesture.delegate = self - selectionGesture.began = { [weak self] in - self?.controller?.cancelPanGesture() - } - selectionGesture.updateIsScrollEnabled = { [weak self] isEnabled in - self?.gridNode.scrollView.isScrollEnabled = isEnabled - } - selectionGesture.itemAt = { [weak self] point in - if let strongSelf = self, let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? MediaPickerGridItemNode, let selectableItem = itemNode.selectableItem { - return (selectableItem, strongSelf.controller?.interaction?.selectionState?.isIdentifierSelected(selectableItem.uniqueIdentifier) ?? false) - } else { - return nil + if case let .assets(_, mode) = controller.subject, case .wallpaper = mode { + + } else { + let selectionGesture = MediaPickerGridSelectionGesture() + selectionGesture.delegate = self + selectionGesture.began = { [weak self] in + self?.controller?.cancelPanGesture() } - } - selectionGesture.updateSelection = { [weak self] asset, selected in - if let strongSelf = self { - strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil) + selectionGesture.updateIsScrollEnabled = { [weak self] isEnabled in + self?.gridNode.scrollView.isScrollEnabled = isEnabled + } + selectionGesture.itemAt = { [weak self] point in + if let strongSelf = self, let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? MediaPickerGridItemNode, let selectableItem = itemNode.selectableItem { + return (selectableItem, strongSelf.controller?.interaction?.selectionState?.isIdentifierSelected(selectableItem.uniqueIdentifier) ?? false) + } else { + return nil + } } + selectionGesture.updateSelection = { [weak self] asset, selected in + if let strongSelf = self { + strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil) + } + } + selectionGesture.sideInset = 44.0 + self.gridNode.view.addGestureRecognizer(selectionGesture) + self.selectionGesture = selectionGesture } - selectionGesture.sideInset = 44.0 - self.gridNode.view.addGestureRecognizer(selectionGesture) - self.selectionGesture = selectionGesture if let controller = self.controller, case let .assets(collection, _) = controller.subject, collection != nil { self.gridNode.view.interactiveTransitionGestureRecognizerTest = { point -> Bool in @@ -741,7 +749,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } if let customSelection = controller.customSelection { + self.openingMedia = true customSelection(fetchResult[index]) + Queue.mainQueue().after(0.3) { + self.openingMedia = false + } return } @@ -2016,6 +2028,7 @@ public func wallpaperMediaPickerController( let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil }) + //controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) controller.requestController = { [weak controller] _, present in let presentationData = context.sharedContext.currentPresentationData.with { $0 } let mediaPickerController = MediaPickerScreen(context: context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper), mainButtonState: AttachmentMainButtonState(text: presentationData.strings.Conversation_Theme_SetColorWallpaper, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true), mainButtonAction: { diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index 454225d3d7d..71c6d650c2b 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -293,7 +293,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa var intensity: Int32? if let brightness { - intensity = Int32(brightness * 100.0) + intensity = max(1, Int32(brightness * 100.0)) } let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: intensity) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 9ead4f5c376..12d8f61d15c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -152,6 +152,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate private let context: AccountContext private var theme: PresentationTheme private let mode: ThemeAccentColorControllerMode + private let resultMode: ThemeAccentColorController.ResultMode private var presentationData: PresentationData private let animationCache: AnimationCache @@ -227,6 +228,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate init(context: AccountContext, mode: ThemeAccentColorControllerMode, resultMode: ThemeAccentColorController.ResultMode, theme: PresentationTheme, wallpaper: TelegramWallpaper, dismiss: @escaping () -> Void, apply: @escaping (ThemeColorState, UIColor?) -> Void, ready: Promise) { self.context = context self.mode = mode + self.resultMode = resultMode self.state = ThemeColorState() self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.theme = theme @@ -766,6 +768,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } else { if case .edit(_, _, _, _, _, true, _) = self.mode { doneButtonType = .proceed + } else if case .peer = self.resultMode { + doneButtonType = .setPeer } else { doneButtonType = .set } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift index 963e1244c24..36a5493534a 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift @@ -394,6 +394,7 @@ public func standaloneColorPickerController( let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil }) + //controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) controller.requestController = { _, present in let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer)) colorPickerController.pushController = { controller in diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 378b2b380a8..f7eb64d7d64 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -1433,7 +1433,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let _ = (signal |> deliverOnMainQueue).start(next: { [weak self] count, timestamp in - if let strongSelf = self, (count < 2 && currentTimestamp > timestamp + 24 * 60 * 60) || "".isEmpty { + 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 diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 5b5e2c9c82f..d7eb301bc47 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10970,6 +10970,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } + if strongSelf.effectiveNavigationController?.topViewController !== strongSelf { + return false + } + if strongSelf.presentationInterfaceState.inputTextPanelState.mediaRecordingState != nil { return false } @@ -18617,6 +18621,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G openWallpaperPickerImpl = openWallpaperPicker openWallpaperPicker() }, + resetWallpaper: { [weak self] in + guard let strongSelf = self, let peerId else { + return + } + let _ = strongSelf.context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil).start() + }, completion: { [weak self] emoticon in guard let strongSelf = self, let peerId else { return @@ -18629,6 +18639,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } ) + controller.navigationPresentation = .flatModal controller.passthroughHitTestImpl = { [weak self] _ in if let strongSelf = self { return strongSelf.chatDisplayNode.historyNode.view @@ -18644,7 +18655,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.historyNode.tapped = { [weak controller] in controller?.dimTapped() } - strongSelf.present(controller, in: .window(.root)) + strongSelf.push(controller) + //strongSelf.present(controller, in: .window(.root)) strongSelf.themeScreen = controller }) } diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index ae3a1955fb3..0b2f0ac4fda 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -557,6 +557,7 @@ final class ChatThemeScreen: ViewController { let canResetWallpaper: Bool private let previewTheme: (String?, Bool?) -> Void fileprivate let changeWallpaper: () -> Void + fileprivate let resetWallpaper: () -> Void private let completion: (String?) -> Void private var presentationData: PresentationData @@ -581,6 +582,7 @@ final class ChatThemeScreen: ViewController { canResetWallpaper: Bool, previewTheme: @escaping (String?, Bool?) -> Void, changeWallpaper: @escaping () -> Void, + resetWallpaper: @escaping () -> Void, completion: @escaping (String?) -> Void ) { self.context = context @@ -591,12 +593,15 @@ final class ChatThemeScreen: ViewController { self.canResetWallpaper = canResetWallpaper self.previewTheme = previewTheme self.changeWallpaper = changeWallpaper + self.resetWallpaper = resetWallpaper self.completion = completion super.init(navigationBarPresentationData: nil) self.statusBar.statusBarStyle = .Ignore + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.blocksBackgroundWhenInOverlay = true self.presentationDataDisposable = (updatedPresentationData.signal @@ -1107,7 +1112,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.setEmoticon(self.initiallySelectedEmoticon) } else { if self.controller?.canResetWallpaper == true { - + self.controller?.resetWallpaper() + self.cancelButtonPressed() } else { self.cancelButtonPressed() } From ceb8619496802a3e4847332f0a8707321375db10 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 9 Apr 2023 02:55:08 +0400 Subject: [PATCH 18/57] Chat wallpaper improvements --- .../TelegramUI/Sources/ChatThemeScreen.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 0b2f0ac4fda..5903d348a6a 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -639,20 +639,20 @@ final class ChatThemeScreen: ViewController { guard let strongSelf = self else { return } - strongSelf.dismiss() + strongSelf.dismiss(animated: true) if strongSelf.initiallySelectedEmoticon == nil && emoticon == nil { } else { strongSelf.completion(emoticon) } } self.controllerNode.dismiss = { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) + self?.dismiss(animated: false) } self.controllerNode.cancel = { [weak self] in guard let strongSelf = self else { return } - strongSelf.dismiss() + strongSelf.dismiss(animated: true) strongSelf.previewTheme(nil, nil) } } @@ -672,15 +672,22 @@ final class ChatThemeScreen: ViewController { } } - override public func dismiss(completion: (() -> Void)? = nil) { + override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { self.forEachController({ controller in if let controller = controller as? TooltipScreen { controller.dismiss() } return true }) - - self.controllerNode.animateOut(completion: completion) + + if flag { + self.controllerNode.animateOut(completion: { + super.dismiss(animated: flag, completion: completion) + completion?() + }) + } else { + super.dismiss(animated: flag, completion: completion) + } self.dismissed?() } From 4fec750fca4350f664eddff50bae970c35d6d573 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 9 Apr 2023 03:28:08 +0400 Subject: [PATCH 19/57] Chat wallpaper improvements --- .../SettingsUI/Sources/Themes/WallpaperGalleryItem.swift | 5 +++-- submodules/TelegramUI/Sources/ChatController.swift | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index f7eb64d7d64..5ac30c58140 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -177,6 +177,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.patternButtonNode.setEnabled(false) self.serviceBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.33)) + self.serviceBackgroundNode.isHidden = true var sliderValueChangedImpl: ((CGFloat) -> Void)? self.sliderNode = WallpaperSliderNode(minValue: 0.0, maxValue: 1.0, value: 0.7, valueChanged: { value, _ in @@ -627,6 +628,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3)) self.wrapperNode.addSubnode(self.cropNode) showPreviewTooltip = true + self.serviceBackgroundNode.isHidden = false case let .contextResult(result): var imageDimensions: CGSize? var imageResource: TelegramMediaResource? @@ -682,11 +684,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode { subtitleSignal = .single(nil) self.wrapperNode.addSubnode(self.cropNode) showPreviewTooltip = true + self.serviceBackgroundNode.isHidden = false } self.contentSize = contentSize - //self.cancelButtonNode.dark = !isEditable - //self.shareButtonNode.dark = !isEditable self.shareButtonNode.isHidden = !canShare if self.cropNode.supernode == nil { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d7eb301bc47..301c4bee33a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -18559,7 +18559,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark))) } }, - changeWallpaper: { + changeWallpaper: { [weak self] in guard let strongSelf = self, let peerId else { return } @@ -18631,6 +18631,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let peerId else { return } + if canResetWallpaper { + let _ = context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil).start() + } strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon ?? "", nil))) let _ = context.engine.themes.setChatTheme(peerId: peerId, emoticon: emoticon).start(completed: { [weak self] in if let strongSelf = self { From 8d7566afec9337a36359ea98567e421460bec039 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 9 Apr 2023 04:50:07 +0400 Subject: [PATCH 20/57] Bot app improvements --- .../TelegramUI/Sources/ChatController.swift | 22 ++++++- .../Sources/ChatTextInputPanelNode.swift | 58 ++++++++++--------- .../Sources/NavigateToChatController.swift | 4 +- .../WebUI/Sources/WebAppController.swift | 2 +- 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 301c4bee33a..5887e0432b6 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -12797,7 +12797,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.presentAttachmentMenu(subject: .bot(id: botId, payload: payload, justInstalled: justInstalled)) } - public func presentBotApp(botApp: BotApp, payload: String?) { + public func presentBotApp(botApp: BotApp, botPeer: EnginePeer, payload: String?) { guard let peerId = self.chatLocation.peerId else { return } @@ -12845,6 +12845,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + let botAddress = botPeer.addressName ?? "" + self.messageActionCallbackDisposable.set(((self.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), allowWrite: false) |> afterDisposed { updateProgress() @@ -12856,6 +12858,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) + }, requestSwitchInline: { [weak self] query, chatTypes, completion in + if let strongSelf = self { + if let chatTypes { + let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false)) + controller.peerSelected = { [weak self, weak controller] peer, _ in + if let strongSelf = self { + completion() + controller?.dismiss() + strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)") + } + } + strongSelf.push(controller) + } else { + strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)") + } + } }, completion: { [weak self] in self?.chatDisplayNode.historyNode.scrollToEndOfHistory() }, getNavigationController: { [weak self] in @@ -17267,7 +17285,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self, let peer { let openBotApp = { [weak self] in if let strongSelf = self { - strongSelf.presentBotApp(botApp: botAppStart.botApp, payload: botAppStart.payload) + strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peerId, payload: botAppStart.payload) } } if concealed { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index aa7835459d7..e92874376b9 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1472,43 +1472,47 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { transition.animatePosition(layer: self.startButton.layer, from: CGPoint(x: 0.0, y: 80.0), to: CGPoint(), additive: true) } - } - - if let context = self.context { - let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil) - let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize()) - - if let tooltipController = self.tooltipController { - if self.view.window != nil { - tooltipController.location = .point(location, .bottom) - } - } else { - let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in - return .ignore - }) - controller.alwaysVisible = true - self.tooltipController = controller + if let context = self.context { + let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil) + let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize()) - let delay: Double - if case .regular = metrics.widthClass { - delay = 0.1 + if let tooltipController = self.tooltipController { + if self.view.window != nil { + tooltipController.location = .point(location, .bottom) + } } else { - delay = 0.35 + let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in + return .ignore + }) + controller.alwaysVisible = true + self.tooltipController = controller + + let delay: Double + if case .regular = metrics.widthClass { + delay = 0.1 + } else { + delay = 0.35 + } + Queue.mainQueue().after(delay, { + let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil) + let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize()) + controller.location = .point(location, .bottom) + self.interfaceInteraction?.presentControllerInCurrent(controller, nil) + }) } - Queue.mainQueue().after(delay, { - let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil) - let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize()) - controller.location = .point(location, .bottom) - self.interfaceInteraction?.presentControllerInCurrent(controller, nil) - }) + } + } else { + if hasMenuButton && !self.animatingTransition { + self.menuButton.isHidden = true } } } else if !self.startButton.isHidden { if hasMenuButton { self.animateBotButtonOutToMenu(transition: transition) } else { - transition.animatePosition(node: self.startButton, to: CGPoint(x: 0.0, y: 80.0), additive: true, completion: { _ in + transition.animatePosition(node: self.startButton, to: CGPoint(x: 0.0, y: 80.0), removeOnCompletion: false, additive: true, completion: { _ in self.startButton.isHidden = true + self.startButton.layer.removeAllAnimations() }) } } diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 396aba0eab4..4776b1c2139 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -132,8 +132,8 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if let attachBotStart = params.attachBotStart { controller.presentAttachmentBot(botId: attachBotStart.botId, payload: attachBotStart.payload, justInstalled: attachBotStart.justInstalled) } - if let botAppStart = params.botAppStart { - controller.presentBotApp(botApp: botAppStart.botApp, payload: botAppStart.payload) + if let botAppStart = params.botAppStart, case let .peer(peer) = params.chatLocation { + controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload) } } else { controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation.asChatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, attachBotStart: params.attachBotStart, botAppStart: params.botAppStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData, chatListFilter: params.chatListFilter, chatNavigationStack: params.chatNavigationStack) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index e1b7eb78164..26c91b9f628 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -629,7 +629,7 @@ public final class WebAppController: ViewController, AttachmentContainable { case "web_app_ready": self.animateTransitionIn() case "web_app_switch_inline_query": - if controller.isInline, let json, let query = json["query"] as? String { + if let json, let query = json["query"] as? String { if let chatTypes = json["chat_types"] as? [String], !chatTypes.isEmpty { var requestPeerTypes: [ReplyMarkupButtonRequestPeerType] = [] for type in chatTypes { From 32de4a1bc74e593d9ef34391085a01a4b3bdc06b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 9 Apr 2023 16:03:17 +0400 Subject: [PATCH 21/57] Chat wallpaper improvements --- .../Sources/Themes/WallpaperGalleryItem.swift | 138 ++++++++++++++++-- .../Themes/WallpaperOptionButtonNode.swift | 8 +- .../TelegramUI/Sources/ChatController.swift | 9 +- .../TelegramUI/Sources/ChatThemeScreen.swift | 12 +- .../Sources/WallpaperResources.swift | 6 +- 5 files changed, 143 insertions(+), 30 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 5ac30c58140..c2087fa7c90 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -314,6 +314,123 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.action?() } + private func switchTheme() { + if let messageNodes = self.messageNodes { + for messageNode in messageNodes.prefix(2) { + if let snapshotView = messageNode.view.snapshotContentTree() { + messageNode.view.addSubview(snapshotView) + snapshotView.frame = messageNode.bounds + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + } + let themeSettings = self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) + |> map { sharedData -> PresentationThemeSettings in + let themeSettings: PresentationThemeSettings + if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) { + themeSettings = current + } else { + themeSettings = PresentationThemeSettings.defaultSettings + } + return themeSettings + } + + let _ = (themeSettings + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] themeSettings in + guard let strongSelf = self else { + return + } + var presentationData = strongSelf.presentationData + + let lightTheme: PresentationTheme + let lightWallpaper: TelegramWallpaper + + let darkTheme: PresentationTheme + let darkWallpaper: TelegramWallpaper + + if !strongSelf.isDarkAppearance { + darkTheme = presentationData.theme + darkWallpaper = presentationData.chatWallpaper + + var currentColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index] + if let colors = currentColors, colors.baseColor == .theme { + currentColors = nil + } + + let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeSettings.theme, accentColor: currentColors)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index]) + + if let themeSpecificWallpaper = themeSpecificWallpaper { + lightWallpaper = themeSpecificWallpaper + } else { + let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, preview: true) ?? defaultPresentationTheme + lightWallpaper = theme.chat.defaultWallpaper + } + + var preferredBaseTheme: TelegramBaseTheme? + if let baseTheme = themeSettings.themePreferredBaseTheme[themeSettings.theme.index], [.classic, .day].contains(baseTheme) { + preferredBaseTheme = baseTheme + } + + lightTheme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeSettings.theme, baseTheme: preferredBaseTheme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme + } else { + lightTheme = presentationData.theme + lightWallpaper = presentationData.chatWallpaper + + let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme + let effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index] + let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index]) + + var preferredBaseTheme: TelegramBaseTheme? + if let baseTheme = themeSettings.themePreferredBaseTheme[automaticTheme.index], [.night, .tinted].contains(baseTheme) { + preferredBaseTheme = baseTheme + } else { + preferredBaseTheme = .night + } + + darkTheme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: automaticTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme + + if let themeSpecificWallpaper = themeSpecificWallpaper { + darkWallpaper = themeSpecificWallpaper + } else { + switch lightWallpaper { + case .builtin, .color, .gradient: + darkWallpaper = darkTheme.chat.defaultWallpaper + case .file: + if lightWallpaper.isPattern { + darkWallpaper = darkTheme.chat.defaultWallpaper + } else { + darkWallpaper = lightWallpaper + } + default: + darkWallpaper = lightWallpaper + } + } + } + + if strongSelf.isDarkAppearance { + darkTheme.forceSync = true + Queue.mainQueue().after(1.0, { + darkTheme.forceSync = false + }) + presentationData = presentationData.withUpdated(theme: darkTheme).withUpdated(chatWallpaper: darkWallpaper) + } else { + lightTheme.forceSync = true + Queue.mainQueue().after(1.0, { + lightTheme.forceSync = false + }) + presentationData = presentationData.withUpdated(theme: lightTheme).withUpdated(chatWallpaper: lightWallpaper) + } + + strongSelf.presentationData = presentationData + + if let (layout, _) = strongSelf.validLayout { + strongSelf.updateMessagesLayout(layout: layout, offset: CGPoint(), transition: .animated(duration: 0.3, curve: .easeInOut)) + } + }) + } @objc private func dayNightPressed() { self.isDarkAppearance = !self.isDarkAppearance @@ -332,6 +449,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.updateIntensity(transition: .animated(duration: 0.3, curve: .easeInOut)) } } + + self.switchTheme() } private func animateIntensityChange(delay: Double) { @@ -1278,7 +1397,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } - let theme = self.presentationData.theme.withUpdated(preview: false) + let theme = self.presentationData.theme let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, isCentered: false)) @@ -1296,18 +1415,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) if let messageNodes = self.messageNodes { - if self.validMessages != [topMessageText, bottomMessageText] { - self.validMessages = [topMessageText, bottomMessageText] - for i in 0 ..< items.count { - items[i].updateNode(async: { f in f() }, node: { return messageNodes[i] }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None) { layout, apply in - let nodeFrame = CGRect(origin: messageNodes[i].frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height)) + for i in 0 ..< items.count { + items[i].updateNode(async: { f in f() }, node: { return messageNodes[i] }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None) { layout, apply in + let nodeFrame = CGRect(origin: messageNodes[i].frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height)) - messageNodes[i].contentSize = layout.contentSize - messageNodes[i].insets = layout.insets - messageNodes[i].frame = nodeFrame + messageNodes[i].contentSize = layout.contentSize + messageNodes[i].insets = layout.insets + messageNodes[i].frame = nodeFrame - apply(ListViewItemApply(isOnScreen: true)) - } + apply(ListViewItemApply(isOnScreen: true)) } } } else { diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 300b6d147d6..ab8bc1b3b48 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -147,7 +147,7 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { case let .dayNight(isNight): title = "" let animationNode = AnimationNode(animation: isNight ? "anim_sun_reverse" : "anim_sun", colors: [:], scale: 1.0) - animationNode.speed = 1.5 + animationNode.speed = 1.66 animationNode.isUserInteractionEnabled = false self.animationNode = animationNode } @@ -202,10 +202,8 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { func setIsNight(_ isNight: Bool) { self.animationNode?.setAnimation(name: !isNight ? "anim_sun_reverse" : "anim_sun", colors: [:]) - self.animationNode?.speed = 1.5 - Queue.mainQueue().after(0.01) { - self.animationNode?.playOnce() - } + self.animationNode?.speed = 1.66 + self.animationNode?.playOnce() } var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 5887e0432b6..a070d072ab5 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -18556,9 +18556,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - - let selectedEmoticon: String? = themeEmoticon - var canResetWallpaper = false if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { canResetWallpaper = cachedUserData.wallpaper != nil @@ -18568,7 +18565,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G context: context, updatedPresentationData: strongSelf.updatedPresentationData, animatedEmojiStickers: animatedEmojiStickers, - initiallySelectedEmoticon: selectedEmoticon, + initiallySelectedEmoticon: themeEmoticon, peerName: strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "", canResetWallpaper: canResetWallpaper, previewTheme: { [weak self] emoticon, dark in @@ -18596,7 +18593,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G navigationController.setViewControllers(controllers, animated: true) } } - var openWallpaperPickerImpl: (() -> Void)? let openWallpaperPicker = { let controller = wallpaperMediaPickerController( @@ -18677,7 +18673,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G controller?.dimTapped() } strongSelf.push(controller) - //strongSelf.present(controller, in: .window(.root)) strongSelf.themeScreen = controller }) } @@ -18711,7 +18706,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) { let isEmoji = actions[0].0.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks - strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_MultipleRemovedText(Int32(actions.count)) : presentationData.strings.StickerPackActionInfo_MultipleRemovedText(Int32(actions.count)), undo: true, info: actions[0].0, topItem: actions[0].1.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { action in if case .undo = action { var itemsAndIndices: [(StickerPackCollectionInfo, [StickerPackItem], Int)] = actions.compactMap { action -> (StickerPackCollectionInfo, [StickerPackItem], Int)? in @@ -18731,7 +18725,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } else if let (info, items, action) = actions.first { let isEmoji = info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks - switch action { case .add: strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 5903d348a6a..035a1566006 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -901,6 +901,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega return } + let selectedEmoticon = selectedEmoticon?.strippedEmoji + let isFirstTime = strongSelf.entries == nil let presentationData = strongSelf.presentationData @@ -910,7 +912,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega guard let emoticon = theme.emoticon else { continue } - entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) + entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon?.strippedEmoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) } let action: (String?) -> Void = { [weak self] emoticon in @@ -993,7 +995,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega var scrollToItem: ListViewScrollToItem? if !self.initialized { if let index = transition.entries.firstIndex(where: { entry in - return entry.emoticon == self.initiallySelectedEmoticon + return entry.emoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji }) { scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down) self.initialized = true @@ -1031,7 +1033,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let otherButtonTitle: String var accentButtonTheme = true var destructiveOtherButton = false - if self.selectedEmoticon == self.initiallySelectedEmoticon { + if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji { doneButtonTitle = self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper otherButtonTitle = canResetWallpaper ? self.presentationData.strings.Conversation_Theme_ResetWallpaper : self.presentationData.strings.Common_Cancel accentButtonTheme = false @@ -1115,7 +1117,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } @objc func otherButtonPressed() { - if self.selectedEmoticon != self.initiallySelectedEmoticon { + if self.selectedEmoticon?.strippedEmoji != self.initiallySelectedEmoticon?.strippedEmoji { self.setEmoticon(self.initiallySelectedEmoticon) } else { if self.controller?.canResetWallpaper == true { @@ -1128,7 +1130,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } func dimTapped() { - if self.selectedEmoticon == self.initiallySelectedEmoticon { + if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji { self.cancelButtonPressed() } else { let alertController = textAlertController(context: self.context, updatedPresentationData: (self.presentationData, .single(self.presentationData)), title: nil, text: self.presentationData.strings.Conversation_Theme_DismissAlert, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_Theme_DismissAlertApply, action: { [weak self] in diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index a4028d56eeb..5cf2e4f5069 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -144,7 +144,11 @@ public func wallpaperDatas(account: Account, accountManager: AccountManager Date: Sun, 9 Apr 2023 17:19:44 +0400 Subject: [PATCH 22/57] Chat wallpaper improvements --- .../Themes/CustomWallpaperPicker.swift | 12 +++++----- .../Sources/Themes/WallpaperGalleryItem.swift | 24 ++++++++++++++----- .../TelegramUI/Sources/ChatController.swift | 7 ++++-- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index 71c6d650c2b..bc08631acdb 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -131,12 +131,12 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE if let data = croppedImage.jpegData(compressionQuality: 0.8), let thumbnailImage = thumbnailImage, let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) { let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) - context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) + context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) + context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) let autoNightModeTriggered = context.sharedContext.currentPresentationData.with {$0 }.autoNightModeTriggered let accountManager = context.sharedContext.accountManager @@ -203,7 +203,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa 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) { - context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, 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() } @@ -211,7 +211,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa for representation in representations { let resource = representation.resource if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() } } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index c2087fa7c90..77098e88394 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -425,6 +425,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } strongSelf.presentationData = presentationData + strongSelf.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) if let (layout, _) = strongSelf.validLayout { strongSelf.updateMessagesLayout(layout: layout, offset: CGPoint(), transition: .animated(duration: 0.3, curve: .easeInOut)) @@ -527,6 +528,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let progressAction = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: presentationData.theme.rootController.navigationBar.accentTextColor)) var isBlurrable = true + + self.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) switch entry { case let .wallpaper(wallpaper, _): @@ -562,8 +565,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } case .asset: self.nativeNode._internalUpdateIsSettingUpWallpaper() - //self.nativeNode.update(wallpaper: self.presentationData.chatWallpaper) - self.nativeNode.isHidden = true + + //self.nativeNode.update(wallpaper: .color(0xff000000)) + self.nativeNode.isHidden = false self.patternButtonNode.isSelected = false self.playButtonNode.setIcon(self.playButtonRotateImage) default: @@ -571,9 +575,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.patternButtonNode.isSelected = false self.playButtonNode.setIcon(self.playButtonRotateImage) } - - self.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) - + var canShare = false switch entry { case let .wallpaper(wallpaper, message): @@ -814,6 +816,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.wrapperNode.addSubnode(self.imageNode) self.wrapperNode.addSubnode(self.nativeNode) } else { + self.wrapperNode.insertSubnode(self.nativeNode, at: 0) self.imageNode.contentMode = .scaleToFill } @@ -833,6 +836,14 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } strongSelf.blurredNode.image = image imagePromise.set(.single(image)) + + if case .asset = entry, let image, let data = image.jpegData(compressionQuality: 0.5) { + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + + let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], WallpaperSettings()) + strongSelf.nativeNode.update(wallpaper: wallpaper) + } } } self.fetchDisposable.set(fetchSignal.start()) @@ -1456,7 +1467,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } if let _ = serviceMessageText, let messageNodes = self.messageNodes, let node = messageNodes.last { - if let backgroundNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.first { + if let backgroundNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.first, let backdropNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.last?.subnodes?.last?.subnodes?.first { + backdropNode.isHidden = true let serviceBackgroundFrame = backgroundNode.view.convert(backgroundNode.bounds, to: self.view).offsetBy(dx: 0.0, dy: -1.0).insetBy(dx: 0.0, dy: -1.0) transition.updateFrame(node: self.serviceBackgroundNode, frame: serviceBackgroundFrame) self.serviceBackgroundNode.update(size: serviceBackgroundFrame.size, cornerRadius: serviceBackgroundFrame.height / 2.0, transition: transition) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index a070d072ab5..710f40a5195 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -18594,12 +18594,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } var openWallpaperPickerImpl: (() -> Void)? - let openWallpaperPicker = { + let openWallpaperPicker = { [weak self] in + guard let strongSelf = self else { + return + } let controller = wallpaperMediaPickerController( context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), - completion: { asset in + completion: { [weak self] asset in guard let strongSelf = self else { return } From 0e5ef7134898323e5b342cf424ce98664ac61ef4 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sun, 9 Apr 2023 23:14:31 +0400 Subject: [PATCH 23/57] Update for Xcode 14.3 --- .../Sources/ChatMessageBubbleMosaicLayout.swift | 2 +- .../Sources/AnimatedStickerUtils.swift | 4 ++-- .../Sources/PresentationGroupCall.swift | 4 ++-- .../Sources/State/PeerInputActivity.swift | 2 +- .../Sources/FetchCachedRepresentations.swift | 16 ++++++++-------- .../Sources/YoutubeEmbedImplementation.swift | 4 ++-- .../Sources/WatchRequestHandlers.swift | 6 +++--- third-party/boringssl/BUILD | 1 + 8 files changed, 20 insertions(+), 19 deletions(-) diff --git a/submodules/MosaicLayout/Sources/ChatMessageBubbleMosaicLayout.swift b/submodules/MosaicLayout/Sources/ChatMessageBubbleMosaicLayout.swift index 134a7584642..58c940fb9ac 100644 --- a/submodules/MosaicLayout/Sources/ChatMessageBubbleMosaicLayout.swift +++ b/submodules/MosaicLayout/Sources/ChatMessageBubbleMosaicLayout.swift @@ -302,7 +302,7 @@ public func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize], innerPositionFlags.insert(.right) } - if positionFlags == .none { + if positionFlags == [] { innerPositionFlags = .inside } diff --git a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift index dcfd793e928..5c52b354377 100644 --- a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift +++ b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift @@ -72,8 +72,8 @@ public func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzM let alphaData = NSMutableData() if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) - CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) + CGImageDestinationSetProperties(alphaDestination, NSDictionary() as CFDictionary) let colorQuality: Float let alphaQuality: Float diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 9af33d1ca0b..f468a4badad 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -286,11 +286,11 @@ private extension CurrentImpl { case .mediaStream: let ssrcId = UInt32.random(in: 0 ..< UInt32(Int32.max - 1)) let dict: [String: Any] = [ - "fingerprints": [], + "fingerprints": [] as [Any], "ufrag": "", "pwd": "", "ssrc": Int32(bitPattern: ssrcId), - "ssrc-groups": [] + "ssrc-groups": [] as [Any] ] guard let jsonString = (try? JSONSerialization.data(withJSONObject: dict, options: [])).flatMap({ String(data: $0, encoding: .utf8) }) else { return .never() diff --git a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift index 2f761b47a79..267fa01d44c 100644 --- a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift +++ b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift @@ -52,7 +52,7 @@ public struct EmojiInteraction: Equatable { fileprivate let roundingBehavior = NSDecimalNumberHandler(roundingMode: .plain, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: true) public var apiDataJson: Api.DataJSON { - let dict = ["v": 1, "a": self.animations.map({ ["i": $0.index, "t": NSDecimalNumber(value: $0.timeOffset).rounding(accordingToBehavior: roundingBehavior)] })] as [String : Any] + let dict = ["v": 1, "a": self.animations.map({ ["i": $0.index, "t": NSDecimalNumber(value: $0.timeOffset).rounding(accordingToBehavior: roundingBehavior)] as [String : Any] })] as [String : Any] if let data = try? JSONSerialization.data(withJSONObject: dict, options: []), let dataString = String(data: data, encoding: .utf8) { return .dataJSON(data: dataString) } else { diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index be5e680b972..bea052c9e83 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -205,8 +205,8 @@ private func fetchCachedStickerAJpegRepresentation(account: Account, resource: M }, scale: 1.0) if let alphaImage = alphaImage, let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) - CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) + CGImageDestinationSetProperties(alphaDestination, NSDictionary() as CFDictionary) let colorQuality: Float let alphaQuality: Float @@ -270,7 +270,7 @@ private func fetchCachedScaledImageRepresentation(resource: MediaResource, resou }, scale: 1.0)! if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) let colorQuality: Float = 0.5 @@ -330,7 +330,7 @@ private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource let url = URL(fileURLWithPath: path) if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) let colorQuality: Float = 0.6 @@ -371,7 +371,7 @@ private func fetchCachedScaledVideoFirstFrameRepresentation(account: Account, re }, scale: 1.0)! if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) let colorQuality: Float = 0.5 @@ -399,7 +399,7 @@ private func fetchCachedBlurredWallpaperRepresentation(resource: MediaResource, let url = URL(fileURLWithPath: path) if let colorImage = blurredImage(image, radius: 20.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) let colorQuality: Float = 0.5 @@ -448,7 +448,7 @@ private func fetchCachedBlurredWallpaperRepresentation(account: Account, resourc let url = URL(fileURLWithPath: path) if let colorImage = blurredImage(image, radius: 20.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) let colorQuality: Float = 0.5 @@ -491,7 +491,7 @@ private func fetchCachedAlbumArtworkRepresentation(account: Account, resource: M })! if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) let colorQuality: Float = 0.5 diff --git a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift index d58ba3931b5..c972da599db 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift @@ -653,10 +653,10 @@ public final class YoutubeEmbedFramePreview: FramePreview { let frame: Int32 = globalFrame % framesOnStoryboard let num: Int32 = Int32(floor(Double(globalFrame) / Double(framesOnStoryboard))) - let url = storyboardUrl(spec: storyboardSpec, sizeIndex: bestSize.0, num: num) + let url = strongSelf.storyboardUrl(spec: storyboardSpec, sizeIndex: bestSize.0, num: num) strongSelf.framePipe.putNext(.waitingForData) - strongSelf.currentFrameDisposable.set(youtubeEmbedStoryboardImage(account: strongSelf.context.account, resource: YoutubeEmbedStoryboardMediaResource(videoId: youtubeImpl.videoId, storyboardId: num, url: url), frame: frame, size: bestSize.1).start(next: { [weak self] image in + strongSelf.currentFrameDisposable.set(youtubeEmbedStoryboardImage(account: strongSelf.context.account, resource: YoutubeEmbedStoryboardMediaResource(videoId: youtubeImpl.videoId, storyboardId: num, url: url), frame: frame, size: bestSize.1).start(next: { image in if let strongSelf = self { if let image = image { strongSelf.framePipe.putNext(.image(image)) diff --git a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift index d53893b6ee5..ad8cf26836d 100644 --- a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift +++ b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift @@ -60,7 +60,7 @@ final class WatchChatListHandler: WatchRequestHandler { users = users.merging(chatUsers, uniquingKeysWith: { (_, last) in last }) } } - subscriber.putNext([ TGBridgeChatsArrayKey: chats, TGBridgeUsersDictionaryKey: users ]) + subscriber.putNext([ TGBridgeChatsArrayKey: chats, TGBridgeUsersDictionaryKey: users ] as [String: Any]) }) return SBlockDisposable { @@ -108,7 +108,7 @@ final class WatchChatMessagesHandler: WatchRequestHandler { users = users.merging(messageUsers, uniquingKeysWith: { (_, last) in last }) } } - subscriber.putNext([ TGBridgeMessagesArrayKey: messages, TGBridgeUsersDictionaryKey: users ]) + subscriber.putNext([ TGBridgeMessagesArrayKey: messages, TGBridgeUsersDictionaryKey: users ] as [String: Any]) }) return SBlockDisposable { @@ -295,7 +295,7 @@ final class WatchPeerInfoHandler: WatchRequestHandler { }) let disposable = signal.start(next: { view in let (chat, users) = makeBridgeChat(peerViewMainPeer(view), view: view) - subscriber.putNext([ TGBridgeChatKey: chat, TGBridgeUsersDictionaryKey: users ]) + subscriber.putNext([ TGBridgeChatKey: chat, TGBridgeUsersDictionaryKey: users ] as [String: Any]) }) return SBlockDisposable { diff --git a/third-party/boringssl/BUILD b/third-party/boringssl/BUILD index be357e69cad..d7972a83558 100644 --- a/third-party/boringssl/BUILD +++ b/third-party/boringssl/BUILD @@ -51,6 +51,7 @@ posix_copts = [ "-Wwrite-strings", "-Wshadow", "-fno-common", + "-Wno-unused-but-set-variable", # Modern build environments should be able to set this to use atomic # operations for reference counting rather than locks. However, it's From 1d114b07c1a4bcea1a239bbc0dc7ef75fecc42a8 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sun, 9 Apr 2023 23:14:44 +0400 Subject: [PATCH 24/57] Bump Xcode version --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index e608eb649cb..3508f160e9d 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { "app": "9.6", "bazel": "6.1.1", - "xcode": "14.2" + "xcode": "14.3" } From dee9c76379a36a47c26739dbf3c94b24cad5bdeb Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sun, 9 Apr 2023 23:15:10 +0400 Subject: [PATCH 25/57] Support device debugging --- Telegram/BUILD | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Telegram/BUILD b/Telegram/BUILD index ac96cc11c65..d737ee8ced1 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -28,6 +28,7 @@ load("@build_bazel_rules_swift//swift:swift.bzl", load( "@rules_xcodeproj//xcodeproj:defs.bzl", "top_level_target", + "top_level_targets", "xcodeproj", ) @@ -1985,9 +1986,12 @@ xcodeproj( bazel_path = telegram_bazel_path, project_name = "Telegram", tags = ["manual"], - top_level_targets = [ - ":Telegram", - ], + top_level_targets = top_level_targets( + labels = [ + ":Telegram", + ], + target_environments = ["device", "simulator"], + ), ) # Temporary targets used to simplify webrtc build tests From 8d53183b65fcf050c439afc762fdd4246de0b751 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 10 Apr 2023 00:10:19 +0400 Subject: [PATCH 26/57] Maybe fix camera crash on capture session stopping --- .../LegacyComponents/PGCameraCaptureSession.h | 3 + .../LegacyComponents/Sources/PGCamera.m | 62 +++++++++---------- .../Sources/PGCameraCaptureSession.m | 18 ++++++ 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/PGCameraCaptureSession.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/PGCameraCaptureSession.h index ab241977cb0..ee08d0cb83b 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/PGCameraCaptureSession.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/PGCameraCaptureSession.h @@ -4,6 +4,7 @@ @class PGCameraMovieWriter; @class PGRectangleDetector; +@class SQueue; @interface PGCameraCaptureSession : AVCaptureSession @@ -65,4 +66,6 @@ + (bool)_isZoomAvailableForDevice:(AVCaptureDevice *)device; ++ (SQueue *)cameraQueue; + @end diff --git a/submodules/LegacyComponents/Sources/PGCamera.m b/submodules/LegacyComponents/Sources/PGCamera.m index af30b63446a..446282eb11e 100644 --- a/submodules/LegacyComponents/Sources/PGCamera.m +++ b/submodules/LegacyComponents/Sources/PGCamera.m @@ -120,7 +120,7 @@ - (void)handleRuntimeError:(NSNotification *)notification TGLegacyLog(@"ERROR: Camera runtime error: %@", notification.userInfo[AVCaptureSessionErrorKey]); __weak PGCamera *weakSelf = self; - TGDispatchAfter(1.5f, [PGCamera cameraQueue]._dispatch_queue, ^ + TGDispatchAfter(1.5f, [PGCameraCaptureSession cameraQueue]._dispatch_queue, ^ { __strong PGCamera *strongSelf = weakSelf; if (strongSelf == nil || strongSelf->_invalidated) @@ -198,7 +198,7 @@ - (void)attachPreviewView:(TGCameraPreviewView *)previewView [previewView setupWithCamera:self]; __weak PGCamera *weakSelf = self; - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { __strong PGCamera *strongSelf = weakSelf; if (strongSelf == nil || strongSelf->_invalidated) @@ -225,7 +225,7 @@ - (void)startCaptureForResume:(bool)resume completion:(void (^)(void))completion if (_invalidated) return; - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { if (self.captureSession.isRunning) return; @@ -261,10 +261,11 @@ - (void)stopCaptureForPause:(bool)pause completion:(void (^)(void))completion TGLegacyLog(@"Camera: stop capture"); - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { if (_invalidated) { +#if !TARGET_IPHONE_SIMULATOR [self.captureSession beginConfiguration]; [self.captureSession resetFlashMode]; @@ -279,16 +280,21 @@ - (void)stopCaptureForPause:(bool)pause completion:(void (^)(void))completion for (AVCaptureOutput *output in self.captureSession.outputs) [self.captureSession removeOutput:output]; -#if !TARGET_IPHONE_SIMULATOR [self.captureSession commitConfiguration]; #endif } TGLegacyLog(@"Camera: stop running"); #if !TARGET_IPHONE_SIMULATOR - [self.captureSession stopRunning]; + @try { + [self.captureSession stopRunning]; + } @catch (NSException *exception) { + TGLegacyLog(@"Camera: caught exception – %@", exception.description); + [self.captureSession commitConfiguration]; + [self.captureSession stopRunning]; + TGLegacyLog(@"Camera: seems to be successfully resolved"); + } #endif - _capturing = false; TGDispatchOnMainThread(^ @@ -328,9 +334,9 @@ - (void)resetTerminal:(bool)__unused terminal synchronous:(bool)synchronous comp }; if (synchronous) - [[PGCamera cameraQueue] dispatchSync:block]; + [[PGCameraCaptureSession cameraQueue] dispatchSync:block]; else - [[PGCamera cameraQueue] dispatch:block]; + [[PGCameraCaptureSession cameraQueue] dispatch:block]; } #pragma mark - @@ -361,7 +367,7 @@ - (void)takePhotoWithCompletion:(void (^)(UIImage *result, PGCameraShotMetadata { bool videoMirrored = !self.disableResultMirroring ? _previewView.captureConnection.videoMirrored : false; - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { if (!self.captureSession.isRunning || self.captureSession.imageOutput.isCapturingStillImage || _invalidated) return; @@ -410,13 +416,13 @@ - (void)takePhotoWithCompletion:(void (^)(UIImage *result, PGCameraShotMetadata if (CFAbsoluteTimeGetCurrent() - _captureStartTime > 0.4) takePhoto(); else - TGDispatchAfter(0.4 - delta, [[PGCamera cameraQueue] _dispatch_queue], takePhoto); + TGDispatchAfter(0.4 - delta, [[PGCameraCaptureSession cameraQueue] _dispatch_queue], takePhoto); }]; } - (void)startVideoRecordingForMoment:(bool)moment completion:(void (^)(NSURL *, CGAffineTransform transform, CGSize dimensions, NSTimeInterval duration, bool success))completion { - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { if (!self.captureSession.isRunning || _invalidated) return; @@ -443,7 +449,7 @@ - (void)startVideoRecordingForMoment:(bool)moment completion:(void (^)(NSURL *, if (CFAbsoluteTimeGetCurrent() - _captureStartTime > 1.5) startRecording(); else - TGDispatchAfter(1.5, [[PGCamera cameraQueue] _dispatch_queue], startRecording); + TGDispatchAfter(1.5, [[PGCameraCaptureSession cameraQueue] _dispatch_queue], startRecording); TGDispatchOnMainThread(^ { @@ -455,7 +461,7 @@ - (void)startVideoRecordingForMoment:(bool)moment completion:(void (^)(NSURL *, - (void)stopVideoRecording { - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { [self.captureSession stopVideoRecording]; @@ -496,7 +502,7 @@ - (void)setCameraMode:(PGCameraMode)cameraMode if (strongSelf == nil) return; - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { strongSelf.captureSession.currentMode = cameraMode; @@ -584,7 +590,7 @@ - (void)setFocusPoint:(CGPoint)point - (void)_setFocusPoint:(CGPoint)point focusMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode monitorSubjectAreaChange:(bool)monitorSubjectAreaChange { - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { if (self.disabled) return; @@ -600,7 +606,7 @@ - (bool)supportsExposureTargetBias - (void)beginExposureTargetBiasChange { - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { if (self.disabled) return; @@ -611,7 +617,7 @@ - (void)beginExposureTargetBiasChange - (void)setExposureTargetBias:(CGFloat)bias { - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { if (self.disabled) return; @@ -622,7 +628,7 @@ - (void)setExposureTargetBias:(CGFloat)bias - (void)endExposureTargetBiasChange { - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { if (self.disabled) return; @@ -661,7 +667,7 @@ - (PGCameraFlashMode)flashMode - (void)setFlashMode:(PGCameraFlashMode)flashMode { - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { self.captureSession.currentFlashMode = flashMode; }]; @@ -689,7 +695,7 @@ - (PGCameraPosition)togglePosition if (strongSelf == nil) return; - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { [strongSelf.captureSession setCurrentCameraPosition:targetCameraPosition]; @@ -744,7 +750,7 @@ - (void)setZoomLevel:(CGFloat)zoomLevel animated:(bool)animated if (self.cameraMode == PGCameraModeVideo) { animated = false; } - [[PGCamera cameraQueue] dispatch:^ + [[PGCameraCaptureSession cameraQueue] dispatch:^ { if (self.disabled) return; @@ -786,18 +792,6 @@ + (bool)hasFrontCamera return ([PGCameraCaptureSession _deviceWithCameraPosition:PGCameraPositionFront] != nil); } -+ (SQueue *)cameraQueue -{ - static dispatch_once_t onceToken; - static SQueue *queue = nil; - dispatch_once(&onceToken, ^ - { - queue = [[SQueue alloc] init]; - }); - - return queue; -} - + (AVCaptureVideoOrientation)_videoOrientationForInterfaceOrientation:(UIInterfaceOrientation)deviceOrientation mirrored:(bool)mirrored { switch (deviceOrientation) diff --git a/submodules/LegacyComponents/Sources/PGCameraCaptureSession.m b/submodules/LegacyComponents/Sources/PGCameraCaptureSession.m index 7d8814b37b8..e071d811169 100644 --- a/submodules/LegacyComponents/Sources/PGCameraCaptureSession.m +++ b/submodules/LegacyComponents/Sources/PGCameraCaptureSession.m @@ -208,6 +208,7 @@ - (bool)isResetNeeded - (void)reset { + NSAssert([[PGCameraCaptureSession cameraQueue] isCurrentQueue], @"[[PGCameraCaptureSession cameraQueue] isCurrentQueue]"); [self beginConfiguration]; [self _removeAudioInputEndAudioSession:true]; @@ -259,6 +260,8 @@ - (PGCameraMode)currentMode - (void)setCurrentMode:(PGCameraMode)mode { + NSAssert([[PGCameraCaptureSession cameraQueue] isCurrentQueue], @"[[PGCameraCaptureSession cameraQueue] isCurrentQueue]"); + _currentMode = mode; [self beginConfiguration]; @@ -804,6 +807,7 @@ - (PGCameraPosition)currentCameraPosition - (void)setCurrentCameraPosition:(PGCameraPosition)position { + NSAssert([[PGCameraCaptureSession cameraQueue] isCurrentQueue], @"[[PGCameraCaptureSession cameraQueue] isCurrentQueue]"); AVCaptureDevice *deviceForTargetPosition = [PGCameraCaptureSession _deviceWithCameraPosition:position]; if ([_videoDevice isEqual:deviceForTargetPosition]) return; @@ -1123,4 +1127,18 @@ - (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArra } } +#pragma mark - + ++ (SQueue *)cameraQueue +{ + static dispatch_once_t onceToken; + static SQueue *queue = nil; + dispatch_once(&onceToken, ^ + { + queue = [[SQueue alloc] init]; + }); + + return queue; +} + @end From 35c8092a900d9e6358e442615fd82a365fad054f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 10 Apr 2023 02:22:56 +0400 Subject: [PATCH 27/57] Chat wallpaper improvements --- .../Sources/Themes/WallpaperGalleryItem.swift | 14 ++++++++------ .../Themes/WallpaperOptionButtonNode.swift | 1 - .../TelegramEngine/Themes/ChatThemes.swift | 4 ++++ .../TelegramUI/Sources/ChatController.swift | 19 +++++++++++++------ .../Sources/WallpaperBackgroundNode.swift | 15 +++++++++++++-- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 77098e88394..02b181312a4 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -425,7 +425,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } strongSelf.presentationData = presentationData - strongSelf.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) if let (layout, _) = strongSelf.validLayout { strongSelf.updateMessagesLayout(layout: layout, offset: CGPoint(), transition: .animated(duration: 0.3, curve: .easeInOut)) @@ -533,7 +532,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { switch entry { case let .wallpaper(wallpaper, _): - self.nativeNode.update(wallpaper: wallpaper) + Queue.mainQueue().justDispatch { + self.nativeNode.update(wallpaper: wallpaper) + } if case let .file(file) = wallpaper, file.isPattern { self.nativeNode.isHidden = false @@ -559,15 +560,14 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.nativeNode.update(wallpaper: wallpaper) self.patternButtonNode.isSelected = false } else { + self.nativeNode._internalUpdateIsSettingUpWallpaper() self.nativeNode.isHidden = true self.patternButtonNode.isSelected = false self.playButtonNode.setIcon(self.playButtonRotateImage) } case .asset: self.nativeNode._internalUpdateIsSettingUpWallpaper() - - //self.nativeNode.update(wallpaper: .color(0xff000000)) - self.nativeNode.isHidden = false + self.nativeNode.isHidden = true self.patternButtonNode.isSelected = false self.playButtonNode.setIcon(self.playButtonRotateImage) default: @@ -1322,6 +1322,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } private func updateMessagesLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) { + self.nativeNode.updateBubbleTheme(bubbleTheme: self.presentationData.theme, bubbleCorners: self.presentationData.chatBubbleCorners) + var bottomInset: CGFloat = 132.0 var items: [ListViewItem] = [] @@ -1468,7 +1470,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { if let _ = serviceMessageText, let messageNodes = self.messageNodes, let node = messageNodes.last { if let backgroundNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.first, let backdropNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.last?.subnodes?.last?.subnodes?.first { - backdropNode.isHidden = true + backdropNode.isHidden = true let serviceBackgroundFrame = backgroundNode.view.convert(backgroundNode.bounds, to: self.view).offsetBy(dx: 0.0, dy: -1.0).insetBy(dx: 0.0, dy: -1.0) transition.updateFrame(node: self.serviceBackgroundNode, frame: serviceBackgroundFrame) self.serviceBackgroundNode.update(size: serviceBackgroundFrame.size, cornerRadius: serviceBackgroundFrame.height / 2.0, transition: transition) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index ab8bc1b3b48..88335906e7d 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -423,7 +423,6 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { func setEnabled(_ enabled: Bool) { let alpha: CGFloat = enabled ? 1.0 : 0.4 - self.backgroundNode.alpha = alpha self.checkNode.alpha = alpha self.colorNode.alpha = alpha self.textNode.alpha = alpha diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index f7be6abafda..b81a8f26f34 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -164,6 +164,10 @@ func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId, return account.postbox.transaction { transaction -> Peer? in if let peer = transaction.getPeer(messageId.peerId), let message = transaction.getMessage(messageId) { if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .setChatWallpaper(wallpaper) = action.action { + var wallpaper = wallpaper + if let settings = settings { + wallpaper = wallpaper.withUpdatedSettings(settings) + } transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in if let current = current as? CachedUserData { return current.withUpdatedWallpaper(wallpaper) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 710f40a5195..186396ff833 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -858,11 +858,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.chatDisplayNode.dismissInput() let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true)) - wallpaperPreviewController.apply = { wallpaper, options, _, _ in - let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, settings: nil) - |> deliverOnMainQueue).start(completed: { [weak wallpaperPreviewController] in - wallpaperPreviewController?.dismiss() - }) + wallpaperPreviewController.apply = { [weak wallpaperPreviewController] entry, options, _, _ in + if case let .wallpaper(wallpaper, _) = entry, case let .file(file) = wallpaper, !file.isPattern && options.contains(.blur) { + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: entry, mode: options, cropRect: nil, brightness: nil, peerId: message.id.peerId, completion: { + wallpaperPreviewController?.dismiss() + }) + } else { + let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, settings: nil) + |> deliverOnMainQueue).start() + Queue.mainQueue().after(0.1) { + wallpaperPreviewController?.dismiss() + } + } } strongSelf.push(wallpaperPreviewController) return true @@ -5861,7 +5868,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var themeEmoticon = themeEmoticon if let themeEmoticonPreview = themeEmoticonPreview { if !themeEmoticonPreview.isEmpty { - if themeEmoticon != themeEmoticonPreview { + if themeEmoticon?.strippedEmoji != themeEmoticonPreview.strippedEmoji { chatWallpaper = nil themeEmoticon = themeEmoticonPreview } diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index af8c9bc9f07..058a4c8ac0b 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -904,8 +904,19 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode return } var dimAlpha: Float = 0.0 - if case let .file(file) = wallpaper, !file.isPattern { - if let intensity = file.settings.intensity, intensity < 100, theme.overallDarkAppearance == true { + if theme.overallDarkAppearance == true { + var intensity: Int32? + switch wallpaper { + case let .image(_, settings): + intensity = settings.intensity + case let .file(file): + if !file.isPattern { + intensity = file.settings.intensity + } + default: + break + } + if let intensity, intensity < 100 { dimAlpha = 1.0 - max(0.0, min(1.0, Float(intensity) / 100.0)) } } From 15ecbd113638c7496f6c988fa44e244e9c3db699 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 10 Apr 2023 17:00:35 +0400 Subject: [PATCH 28/57] Chat wallpaper fixes --- .../Sources/WallpaperBackgroundNode.swift | 1 + submodules/WallpaperResources/Sources/WallpaperResources.swift | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 058a4c8ac0b..230b208e501 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -864,6 +864,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.bakedBackgroundView.isHidden = true self.dimLayer = SimpleLayer() + self.dimLayer.opacity = 0.0 self.dimLayer.backgroundColor = UIColor.black.cgColor super.init() diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index 5cf2e4f5069..b85cdf200d3 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -258,7 +258,7 @@ public func wallpaperImage(account: Account, accountManager: AccountManager Date: Mon, 10 Apr 2023 17:55:46 +0400 Subject: [PATCH 29/57] Support Xcode-managed codesigning and update build instructions --- README.md | 85 +++++++++---------- Telegram/BUILD | 21 ++++- .../Sources/NotificationService.swift | 2 +- Telegram/SiriIntents/IntentHandler.swift | 2 +- WORKSPACE | 6 ++ build-system/Make/BuildConfiguration.py | 32 ++++++- build-system/Make/Make.py | 29 ++++--- build-system/Make/ProjectGeneration.py | 20 +++-- ...ate_minimal_development_configuration.json | 14 +++ submodules/BuildConfig/BUILD | 4 + .../PublicHeaders/BuildConfig/BuildConfig.h | 2 + submodules/BuildConfig/Sources/BuildConfig.m | 8 ++ .../Sources/Network/Network.swift | 6 +- .../TelegramUI/Sources/AppDelegate.swift | 10 ++- .../Sources/NotificationContentContext.swift | 2 +- .../Sources/ShareExtensionContext.swift | 2 +- 16 files changed, 170 insertions(+), 75 deletions(-) create mode 100755 build-system/template_minimal_development_configuration.json diff --git a/README.md b/README.md index eceb040fe9d..a2e8da75f61 100644 --- a/README.md +++ b/README.md @@ -11,86 +11,85 @@ There are several things we require from **all developers** for the moment. 3. Please study our [**security guidelines**](https://core.telegram.org/mtproto/security_guidelines) and take good care of your users' data and privacy. 4. Please remember to publish **your** code too in order to comply with the licences. -# Compilation Guide +# Quick Compilation Guide -1. Install Xcode (directly from https://developer.apple.com/download/more or using the App Store). -2. Clone the project from GitHub: +## Get the Code ``` git clone --recursive -j8 https://github.com/TelegramMessenger/Telegram-iOS.git ``` -3. Adjust configuration parameters +## Setup Xcode -``` -mkdir -p $HOME/telegram-configuration -mkdir -p $HOME/telegram-provisioning -cp build-system/appstore-configuration.json $HOME/telegram-configuration/configuration.json -cp -R build-system/fake-codesigning $HOME/telegram-provisioning/ -``` +Install Xcode (directly from https://developer.apple.com/download/applications or using the App Store). -- Modify the values in `configuration.json` -- Replace the provisioning profiles in `profiles` with valid files - -4. (Optional) Create a build cache directory to speed up rebuilds +## Adjust Configuration +1. Generate a random identifier: ``` -mkdir -p "$HOME/telegram-bazel-cache" +openssl rand -hex 8 ``` +2. Create a new Xcode project. Use `Telegram` as the Product Name. Use `org.{identifier from step 1}` as the Organization Identifier. +3. In Signing & Capabilities click on the `i` next to `Xcode Managed Profile` and copy the team ID (`ABCDEFG123`). +4. Edit `build-system/template_minimal_development_configuration.json`. Use data from the previous steps. -5. Build the app +## Generate an Xcode project ``` python3 build-system/Make/Make.py \ --cacheDir="$HOME/telegram-bazel-cache" \ - build \ - --configurationPath=path-to-configuration.json \ - --codesigningInformationPath=path-to-provisioning-data \ - --buildNumber=100001 \ - --configuration=release_universal + generateProject \ + --configurationPath=build-system/template_minimal_development_configuration.json \ + --xcodeManagedCodesigning ``` -6. (Optional) Generate an Xcode project +# Advanced Compilation Guide + +## Xcode +1. Copy and edit `build-system/appstore-configuration.json`. +2. Copy `build-system/fake-codesigning`. Create and download provisioning profiles, using the `profiles` folder as a reference for the entitlements. +3. Generate an Xcode project: ``` python3 build-system/Make/Make.py \ --cacheDir="$HOME/telegram-bazel-cache" \ generateProject \ - --configurationPath=path-to-configuration.json \ - --codesigningInformationPath=path-to-provisioning-data \ - --disableExtensions + --configurationPath=configuration_from_step_1.json \ + --codesigningInformationPath=directory_from_step_2 ``` -It is possible to generate a project that does not require any codesigning certificates to be installed: add `--disableProvisioningProfiles` flag: +## IPA + +1. Repeat the steps from the previous section. Use distribution provisioning profiles. +2. Run: ``` python3 build-system/Make/Make.py \ --cacheDir="$HOME/telegram-bazel-cache" \ - generateProject \ - --configurationPath=path-to-configuration.json \ - --codesigningInformationPath=path-to-provisioning-data \ - --disableExtensions \ - --disableProvisioningProfiles + build \ + --configurationPath=...see previous section... \ + --codesigningInformationPath=...see previous section... \ + --buildNumber=100001 \ + --configuration=release_arm64 ``` +## Tips -Tip: use `--disableExtensions` when developing to speed up development by not building application extensions and the WatchOS app. - - -# Tips - -Bazel is used to build the app. To simplify the development setup a helper script is provided (`build-system/Make/Make.py`). See help: +## Codesigning is not required for simulator-only builds +Add `--disableProvisioningProfiles`: ``` -python3 build-system/Make/Make.py --help -python3 build-system/Make/Make.py build --help -python3 build-system/Make/Make.py generateProject --help +python3 build-system/Make/Make.py \ + --cacheDir="$HOME/telegram-bazel-cache" \ + generateProject \ + --configurationPath=path-to-configuration.json \ + --codesigningInformationPath=path-to-provisioning-data \ + --disableProvisioningProfiles ``` -Bazel is automatically downloaded when running Make.py for the first time. If you wish to use your own build of Bazel, pass `--bazel=path-to-bazel`. If your Bazel version differs from that in `versions.json`, you may use `--overrideBazelVersion` to skip the version check. +## Versions -Each release is built using specific Xcode and Bazel versions (see `versions.json`). The helper script checks the versions of installed software and reports an error if they don't match the ones specified in `versions.json`. There are flags that allow to bypass these checks: +Each release is built using a specific Xcode version (see `versions.json`). The helper script checks the versions of the installed software and reports an error if they don't match the ones specified in `versions.json`. It is possible to bypass these checks: ``` -python3 build-system/Make/Make.py --overrideBazelVersion build ... # Don't check the version of Bazel python3 build-system/Make/Make.py --overrideXcodeVersion build ... # Don't check the version of Xcode ``` diff --git a/Telegram/BUILD b/Telegram/BUILD index d737ee8ced1..8449b491b17 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -30,8 +30,11 @@ load( "top_level_target", "top_level_targets", "xcodeproj", + "xcode_provisioning_profile", ) +load("@build_bazel_rules_apple//apple:apple.bzl", "local_provisioning_profile") + load("//build-system/bazel-utils:plist_fragment.bzl", "plist_fragment", ) @@ -39,6 +42,7 @@ load("//build-system/bazel-utils:plist_fragment.bzl", load( "@build_configuration//:variables.bzl", "telegram_bazel_path", + "telegram_use_xcode_managed_codesigning", "telegram_bundle_id", "telegram_aps_environment", "telegram_team_id", @@ -510,10 +514,11 @@ app_groups_fragment = """ telegram_bundle_id=telegram_bundle_id ) -communication_notifications_fragment = """ +official_communication_notifications_fragment = """ com.apple.developer.usernotifications.communication """ +communication_notifications_fragment = official_communication_notifications_fragment if telegram_bundle_id in official_bundle_ids else "" store_signin_fragment = """ com.apple.developer.applesignin @@ -1920,6 +1925,18 @@ plist_fragment( ) ) +local_provisioning_profile( + name = "Telegram_local_profile", + profile_name = "iOS Team Provisioning Profile: {}".format(telegram_bundle_id), + team_id = telegram_team_id, +) + +xcode_provisioning_profile( + name = "Telegram_xcode_profile", + managed_by_xcode = True, + provisioning_profile = ":Telegram_local_profile", +) + ios_application( name = "Telegram", bundle_id = "{telegram_bundle_id}".format( @@ -1929,7 +1946,7 @@ ios_application( minimum_os_version = minimum_os_version, provisioning_profile = select({ ":disableProvisioningProfilesSetting": None, - "//conditions:default": "@build_configuration//provisioning:Telegram.mobileprovision", + "//conditions:default": ":Telegram_xcode_profile" if telegram_use_xcode_managed_codesigning else "@build_configuration//provisioning:Telegram.mobileprovision", }), entitlements = ":TelegramEntitlements.entitlements", infoplists = [ diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index f3a1f0d7f50..bb521192d2c 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -680,7 +680,7 @@ private final class NotificationServiceHandler { Logger.shared.logToConsole = loggingSettings.logToConsole Logger.shared.redactSensitiveData = loggingSettings.redactSensitiveData - let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild) + let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false) let isLockedMessage: String? if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { diff --git a/Telegram/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift index 17641dfdfd8..09fab9493bc 100644 --- a/Telegram/SiriIntents/IntentHandler.swift +++ b/Telegram/SiriIntents/IntentHandler.swift @@ -174,7 +174,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo if let accountCache = accountCache { account = .single(accountCache) } else { - account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters) + account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters) |> mapToSignal { account -> Signal in if let account = account { switch account { diff --git a/WORKSPACE b/WORKSPACE index fa23cf7b1a8..330d06cb048 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -75,3 +75,9 @@ http_archive( sha256 = "032907801dc7784744a1ca8fd40d3eecc34a2e27a93a4b3993f617cca204a9f3", build_file = "@//third-party/AppCenter:AppCenter.BUILD", ) + +load("@build_bazel_rules_apple//apple:apple.bzl", "provisioning_profile_repository") + +provisioning_profile_repository( + name = "local_provisioning_profiles", +) diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index 92f85d8f2be..8407dad9c5a 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -35,9 +35,10 @@ def __init__(self, self.enable_siri = enable_siri self.enable_icloud = enable_icloud - def write_to_variables_file(self, bazel_path, aps_environment, path): + def write_to_variables_file(self, bazel_path, use_xcode_managed_codesigning, aps_environment, path): string = '' string += 'telegram_bazel_path = "{}"\n'.format(bazel_path) + string += 'telegram_use_xcode_managed_codesigning = {}\n'.format('True' if use_xcode_managed_codesigning else 'False') string += 'telegram_bundle_id = "{}"\n'.format(self.bundle_id) string += 'telegram_api_id = "{}"\n'.format(self.api_id) string += 'telegram_api_hash = "{}"\n'.format(self.api_hash) @@ -240,6 +241,9 @@ def copy_profiles_to_destination(self, destination_path): raise Exception('Not implemented') def resolve_aps_environment(self): + raise Exception('Not implemented') + + def use_xcode_managed_codesigning(self): raise Exception('Not implemented') def copy_certificates_to_destination(self, destination_path): @@ -280,6 +284,9 @@ def resolve_aps_environment(self): source_path = self.working_dir + '/decrypted/profiles/{}'.format(self.codesigning_type) return resolve_aps_environment_from_directory(source_path=source_path, team_id=self.team_id, bundle_id=self.bundle_id) + def use_xcode_managed_codesigning(self): + return False + def copy_certificates_to_destination(self, destination_path): source_path = None if self.codesigning_type in ['adhoc', 'appstore']: @@ -308,5 +315,28 @@ def copy_profiles_to_destination(self, destination_path): def resolve_aps_environment(self): return resolve_aps_environment_from_directory(source_path=self.directory_path + '/profiles', team_id=self.team_id, bundle_id=self.bundle_id) + def use_xcode_managed_codesigning(self): + return False + def copy_certificates_to_destination(self, destination_path): copy_certificates_from_directory(source_path=self.directory_path + '/certs', destination_path=destination_path) + + +class XcodeManagedCodesigningSource(CodesigningSource): + def __init__(self): + pass + + def load_data(self, working_dir): + pass + + def copy_profiles_to_destination(self, destination_path): + pass + + def resolve_aps_environment(self): + return "" + + def use_xcode_managed_codesigning(self): + return True + + def copy_certificates_to_destination(self, destination_path): + pass diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index 6506859acbc..2fc8f25a7f5 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -12,14 +12,15 @@ from BuildEnvironment import resolve_executable, call_executable, run_executable_with_output, BuildEnvironment from ProjectGeneration import generate from BazelLocation import locate_bazel -from BuildConfiguration import CodesigningSource, GitCodesigningSource, DirectoryCodesigningSource, BuildConfiguration, build_configuration_from_json +from BuildConfiguration import CodesigningSource, GitCodesigningSource, DirectoryCodesigningSource, XcodeManagedCodesigningSource, BuildConfiguration, build_configuration_from_json import RemoteBuild import GenerateProfiles class ResolvedCodesigningData: - def __init__(self, aps_environment): + def __init__(self, aps_environment, use_xcode_managed_codesigning): self.aps_environment = aps_environment + self.use_xcode_managed_codesigning = use_xcode_managed_codesigning class BazelCommandLine: @@ -446,11 +447,9 @@ def resolve_codesigning(arguments, base_path, build_configuration, provisioning_ always_fetch=not arguments.gitCodesigningUseCurrent ) elif arguments.codesigningInformationPath is not None: - profile_source = DirectoryCodesigningSource( - directory_path=arguments.codesigningInformationPath, - team_id=build_configuration.team_id, - bundle_id=build_configuration.bundle_id - ) + profile_source = DirectoryCodesigningSource() + elif arguments.xcodeManagedCodesigning is not None and arguments.xcodeManagedCodesigning == True: + profile_source = XcodeManagedCodesigningSource() elif arguments.noCodesigning is not None: return ResolvedCodesigningData(aps_environment='production') else: @@ -467,7 +466,10 @@ def resolve_codesigning(arguments, base_path, build_configuration, provisioning_ profile_source.copy_profiles_to_destination(destination_path=additional_codesigning_output_path + '/profiles') profile_source.copy_certificates_to_destination(destination_path=additional_codesigning_output_path + '/certs') - return ResolvedCodesigningData(aps_environment=profile_source.resolve_aps_environment()) + return ResolvedCodesigningData( + aps_environment=profile_source.resolve_aps_environment(), + use_xcode_managed_codesigning=profile_source.use_xcode_managed_codesigning() + ) def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, arguments, additional_codesigning_output_path): @@ -499,7 +501,7 @@ def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, argum sys.exit(1) if bazel_command_line is not None: - build_configuration.write_to_variables_file(bazel_path=bazel_command_line.bazel, aps_environment=codesigning_data.aps_environment, path=configuration_repository_path + '/variables.bzl') + build_configuration.write_to_variables_file(bazel_path=bazel_command_line.bazel, use_xcode_managed_codesigning=codesigning_data.use_xcode_managed_codesigning, aps_environment=codesigning_data.aps_environment, path=configuration_repository_path + '/variables.bzl') provisioning_profile_files = [] for file_name in os.listdir(provisioning_path): @@ -549,6 +551,8 @@ def generate_project(bazel, arguments): disable_extensions = arguments.disableExtensions if arguments.disableProvisioningProfiles is not None: disable_provisioning_profiles = arguments.disableProvisioningProfiles + if arguments.xcodeManagedCodesigning is not None and arguments.xcodeManagedCodesigning == True: + disable_extensions = True if arguments.generateDsym is not None: generate_dsym = arguments.generateDsym if arguments.target is not None: @@ -688,12 +692,11 @@ def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser): metavar='command' ) codesigning_group.add_argument( - '--noCodesigning', - type=bool, + '--xcodeManagedCodesigning', + action='store_true', help=''' - Use signing certificates and provisioning profiles from a local directory. + Let Xcode manage your certificates and provisioning profiles. ''', - metavar='command' ) current_parser.add_argument( diff --git a/build-system/Make/ProjectGeneration.py b/build-system/Make/ProjectGeneration.py index 8d021208cd0..046ee4d9bdc 100644 --- a/build-system/Make/ProjectGeneration.py +++ b/build-system/Make/ProjectGeneration.py @@ -10,21 +10,27 @@ def remove_directory(path): shutil.rmtree(path) def generate_xcodeproj(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments, target_name): + if '/' in target_name: + app_target_spec = target_name.split('/')[0] + '/' + target_name.split('/')[1] + ':' + target_name.split('/')[1] + app_target = target_name + app_target_clean = app_target.replace('/', '_') + else: + app_target_spec = '{target}:{target}'.format(target=target_name) + app_target = target_name + app_target_clean = app_target.replace('/', '_') + bazel_generate_arguments = [build_environment.bazel_path] bazel_generate_arguments += ['run', '//Telegram:Telegram_xcodeproj'] bazel_generate_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)] - #if disable_extensions: - # bazel_generate_arguments += ['--//{}:disableExtensions'.format(app_target)] - #if disable_provisioning_profiles: - # bazel_generate_arguments += ['--//{}:disableProvisioningProfiles'.format(app_target)] - #if generate_dsym: - # bazel_generate_arguments += ['--apple_generate_dsym'] - #bazel_generate_arguments += ['--//{}:disableStripping'.format('Telegram')] + if disable_extensions: + bazel_generate_arguments += ['--//{}:disableExtensions'.format(app_target)] project_bazel_arguments = [] for argument in bazel_app_arguments: project_bazel_arguments.append(argument) project_bazel_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)] + if disable_extensions: + project_bazel_arguments += ['--//{}:disableExtensions'.format(app_target)] xcodeproj_bazelrc = os.path.join(build_environment.base_path, 'xcodeproj.bazelrc') if os.path.isfile(xcodeproj_bazelrc): diff --git a/build-system/template_minimal_development_configuration.json b/build-system/template_minimal_development_configuration.json new file mode 100755 index 00000000000..1aad0aed955 --- /dev/null +++ b/build-system/template_minimal_development_configuration.json @@ -0,0 +1,14 @@ +{ + "bundle_id": "org.{! a random string !}.Telegram", + "api_id": "{! get one at https://my.telegram.org/apps !}", + "api_hash": "{! get one at https://my.telegram.org/apps !}", + "team_id": "{! check README.md !}", + "app_center_id": "0", + "is_internal_build": "true", + "is_appstore_build": "false", + "appstore_id": "0", + "app_specific_url_scheme": "tg", + "premium_iap_product_id": "", + "enable_siri": false, + "enable_icloud": false +} \ No newline at end of file diff --git a/submodules/BuildConfig/BUILD b/submodules/BuildConfig/BUILD index 10c5c92dbba..7ac35f1be8a 100644 --- a/submodules/BuildConfig/BUILD +++ b/submodules/BuildConfig/BUILD @@ -7,6 +7,8 @@ load( "telegram_is_appstore_build", "telegram_appstore_id", "telegram_app_specific_url_scheme", + "telegram_enable_icloud", + "telegram_enable_siri", ) objc_library( @@ -25,6 +27,8 @@ objc_library( "-DAPP_CONFIG_IS_APPSTORE_BUILD={}".format(telegram_is_appstore_build), "-DAPP_CONFIG_APPSTORE_ID={}".format(telegram_appstore_id), "-DAPP_SPECIFIC_URL_SCHEME=\\\"{}\\\"".format(telegram_app_specific_url_scheme), + "-DAPP_CONFIG_IS_ICLOUD_ENABLED={}".format("true" if telegram_enable_icloud else "false"), + "-DAPP_CONFIG_IS_SIRI_ENABLED={}".format("true" if telegram_enable_siri else "false"), ], hdrs = glob([ "PublicHeaders/**/*.h", diff --git a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h index d735dd74bca..f37dd5ebce8 100644 --- a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h +++ b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h @@ -18,6 +18,8 @@ @property (nonatomic, readonly) bool isAppStoreBuild; @property (nonatomic, readonly) int64_t appStoreId; @property (nonatomic, strong, readonly) NSString * _Nonnull appSpecificUrlScheme; +@property (nonatomic, readonly) bool isICloudEnabled; +@property (nonatomic, readonly) bool isSiriEnabled; + (DeviceSpecificEncryptionParameters * _Nonnull)deviceSpecificEncryptionParameters:(NSString * _Nonnull)rootPath baseAppBundleId:(NSString * _Nonnull)baseAppBundleId; - (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken signatureDict:(NSDictionary * _Nullable)signatureDict; diff --git a/submodules/BuildConfig/Sources/BuildConfig.m b/submodules/BuildConfig/Sources/BuildConfig.m index 3dde1749aee..ba2637e2022 100644 --- a/submodules/BuildConfig/Sources/BuildConfig.m +++ b/submodules/BuildConfig/Sources/BuildConfig.m @@ -185,6 +185,14 @@ - (NSString *)appSpecificUrlScheme { return @(APP_SPECIFIC_URL_SCHEME); } +- (bool)isICloudEnabled { + return APP_CONFIG_IS_ICLOUD_ENABLED; +} + +- (bool)isSiriEnabled { + return APP_CONFIG_IS_SIRI_ENABLED; +} + + (NSString * _Nullable)bundleSeedId { NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: (__bridge NSString *)kSecClassGenericPassword, (__bridge NSString *)kSecClass, diff --git a/submodules/TelegramCore/Sources/Network/Network.swift b/submodules/TelegramCore/Sources/Network/Network.swift index 23b71f1ef25..61ccf91ab4e 100644 --- a/submodules/TelegramCore/Sources/Network/Network.swift +++ b/submodules/TelegramCore/Sources/Network/Network.swift @@ -434,8 +434,9 @@ public struct NetworkInitializationArguments { public let encryptionProvider: EncryptionProvider public let deviceModelName:String? public let useBetaFeatures: Bool + public let isICloudEnabled: Bool - public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal, autolockDeadine: Signal, encryptionProvider: EncryptionProvider, deviceModelName: String?, useBetaFeatures: Bool) { + public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal, autolockDeadine: Signal, encryptionProvider: EncryptionProvider, deviceModelName: String?, useBetaFeatures: Bool, isICloudEnabled: Bool) { self.apiId = apiId self.apiHash = apiHash self.languagesCategory = languagesCategory @@ -447,6 +448,7 @@ public struct NetworkInitializationArguments { self.encryptionProvider = encryptionProvider self.deviceModelName = deviceModelName self.useBetaFeatures = useBetaFeatures + self.isICloudEnabled = isICloudEnabled } } #if os(iOS) @@ -541,7 +543,7 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa context.keychain = keychain var wrappedAdditionalSource: MTSignal? #if os(iOS) - if #available(iOS 10.0, *), !supplementary { + if #available(iOS 10.0, *), !supplementary, arguments.isICloudEnabled { var cloudDataContextValue: CloudDataContext? if let value = cloudDataContext.with({ $0 }) { cloudDataContextValue = value diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index d77804fefc6..78a5260652c 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -483,7 +483,7 @@ private func extractAccountManagerState(records: AccountRecordsView Date: Mon, 10 Apr 2023 17:58:59 +0400 Subject: [PATCH 30/57] Fix build --- build-system/Make/Make.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index 2fc8f25a7f5..385129e0cd6 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -447,7 +447,11 @@ def resolve_codesigning(arguments, base_path, build_configuration, provisioning_ always_fetch=not arguments.gitCodesigningUseCurrent ) elif arguments.codesigningInformationPath is not None: - profile_source = DirectoryCodesigningSource() + profile_source = DirectoryCodesigningSource( + directory_path=arguments.codesigningInformationPath, + team_id=build_configuration.team_id, + bundle_id=build_configuration.bundle_id + ) elif arguments.xcodeManagedCodesigning is not None and arguments.xcodeManagedCodesigning == True: profile_source = XcodeManagedCodesigningSource() elif arguments.noCodesigning is not None: From d7027eb76ccd793598717bf1f1e81efbac376f68 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 10 Apr 2023 18:05:47 +0400 Subject: [PATCH 31/57] Fix build --- build-system/Make/Make.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index 385129e0cd6..708145fd2ce 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -454,8 +454,6 @@ def resolve_codesigning(arguments, base_path, build_configuration, provisioning_ ) elif arguments.xcodeManagedCodesigning is not None and arguments.xcodeManagedCodesigning == True: profile_source = XcodeManagedCodesigningSource() - elif arguments.noCodesigning is not None: - return ResolvedCodesigningData(aps_environment='production') else: raise Exception('Neither gitCodesigningRepository nor codesigningInformationPath are set') @@ -602,9 +600,6 @@ def build(bazel, arguments): bazel_command_line.set_show_actions(arguments.showActions) bazel_command_line.set_enable_sandbox(arguments.sandbox) - if arguments.noCodesigning is not None: - bazel_command_line.set_disable_provisioning_profiles() - bazel_command_line.set_split_swiftmodules(arguments.enableParallelSwiftmoduleGeneration) bazel_command_line.invoke_build() From 4b6f32fc1740c5c202ee920877614615b0e2636b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 10 Apr 2023 18:54:40 +0400 Subject: [PATCH 32/57] Chat wallpaper improvements --- .../Themes/CustomWallpaperPicker.swift | 30 +- .../Themes/ThemePreviewController.swift | 6 +- .../Themes/ThemePreviewControllerNode.swift | 3 + .../Sources/Themes/WallpaperGalleryItem.swift | 4 +- .../Themes/WallpaperOptionButtonNode.swift | 6 +- .../Sources/Account/Account.swift | 6 +- .../PendingPeerMediaUploadManager.swift | 403 ++++++++++++++++++ .../TelegramEngine/Themes/ChatThemes.swift | 16 +- .../Themes/TelegramEngineThemes.swift | 5 +- .../TelegramCore/Sources/Wallpapers.swift | 16 +- .../TelegramUI/Sources/ChatController.swift | 2 +- .../ChatMessageInteractiveMediaNode.swift | 6 +- ...hatMessageWallpaperBubbleContentNode.swift | 75 +++- .../Sources/WallpaperPreviewMedia.swift | 3 + 14 files changed, 531 insertions(+), 50 deletions(-) create mode 100644 submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index bc08631acdb..59fd6e67c84 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -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 + var imageSignal: Signal 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() diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 8307d58d62a..49426bc211e 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -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) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 55892a00718..dbd2386ad8b 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -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 } }) } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 02b181312a4..410e741e344 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -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) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 88335906e7d..25649e2875a 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -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 diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 2362832a498..a478bb3e8a6 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -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] = [ + let extractedExpr: [Signal] = [ 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] = extractedExpr let importantBackgroundOperationsRunning = combineLatest(queue: Queue(), importantBackgroundOperations) |> map { values -> AccountRunningImportantTasks in var result: AccountRunningImportantTasks = [] diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift new file mode 100644 index 00000000000..dabe3847dfd --- /dev/null +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift @@ -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 { + 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 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 { + 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 + + 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 { + 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 + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index b81a8f26f34..dc247c1097a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -118,14 +118,14 @@ func managedChatThemesUpdates(accountManager: AccountManager then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } -func _internal_setChatWallpaper(account: Account, peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal { - return account.postbox.loadedPeerWithId(peerId) +func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, wallpaper: TelegramWallpaper?, applyUpdates: Bool = true) -> Signal { + return postbox.loadedPeerWithId(peerId) |> mapToSignal { peer in guard let inputPeer = apiInputPeer(peer) else { return .complete() } - return account.postbox.transaction { transaction -> Signal in + return postbox.transaction { transaction -> Signal 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 in - account.stateManager.addUpdates(updates) - return .complete() + |> mapToSignal { updates -> Signal in + if applyUpdates { + stateManager.addUpdates(updates) + } + return .single(updates) } } |> switchToLatest } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift index 3e60af86f6c..623efabd58d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift @@ -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 { - return _internal_setChatWallpaper(account: self.account, peerId: peerId, wallpaper: wallpaper) + public func setChatWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal { + 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 { diff --git a/submodules/TelegramCore/Sources/Wallpapers.swift b/submodules/TelegramCore/Sources/Wallpapers.swift index c9629964306..68eff19da9e 100644 --- a/submodules/TelegramCore/Sources/Wallpapers.swift +++ b/submodules/TelegramCore/Sources/Wallpapers.swift @@ -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 { - 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 { + 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) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 186396ff833..51186791a95 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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))) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index 3d10f3ae399..603e1b46aeb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -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)) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift index 0c25ebe888d..589677da9a9 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift b/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift index 04ce3d01be2..0a232c2355d 100644 --- a/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift +++ b/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift @@ -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 } From 6179c3a939aab6b713328d477e68fd27a5ed2f17 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 10 Apr 2023 20:37:13 +0400 Subject: [PATCH 33/57] Add support for peer type filtering in switch inline --- .../ReplyMarkupMessageAttribute.swift | 21 ++++++++- ...SyncCore_ReplyMarkupMessageAttribute.swift | 45 +++++++++++++++++-- .../Sources/ChatControllerInteraction.swift | 4 +- .../Sources/ChatButtonKeyboardInputNode.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 16 +++---- .../Sources/ChatMessageItemView.swift | 4 +- .../ChatPinnedMessageTitlePanelNode.swift | 4 +- .../ChatRecentActionsControllerNode.swift | 2 +- .../OverlayAudioPlayerControllerNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 2 +- .../Sources/SharedAccountContext.swift | 2 +- 11 files changed, 80 insertions(+), 24 deletions(-) diff --git a/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift index 9ac86e8e8db..00f0da59161 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift @@ -17,8 +17,25 @@ extension ReplyMarkupButton { self.init(title: text, titleWhenForwarded: nil, action: .requestMap) case let .keyboardButtonRequestPhone(text): self.init(title: text, titleWhenForwarded: nil, action: .requestPhone) - case let .keyboardButtonSwitchInline(flags, text, query, _): - self.init(title: text, titleWhenForwarded: nil, action: .switchInline(samePeer: (flags & (1 << 0)) != 0, query: query)) + case let .keyboardButtonSwitchInline(flags, text, query, types): + var peerTypes = ReplyMarkupButtonAction.PeerTypes() + if let types = types { + for type in types { + switch type { + case .inlineQueryPeerTypePM: + peerTypes.insert(.users) + case .inlineQueryPeerTypeBotPM: + peerTypes.insert(.bots) + case .inlineQueryPeerTypeBroadcast: + peerTypes.insert(.channels) + case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup: + peerTypes.insert(.groups) + case .inlineQueryPeerTypeSameBotPM: + break + } + } + } + self.init(title: text, titleWhenForwarded: nil, action: .switchInline(samePeer: (flags & (1 << 0)) != 0, query: query, peerTypes: peerTypes)) case let .keyboardButtonUrl(text, url): self.init(title: text, titleWhenForwarded: nil, action: .url(url)) case let .keyboardButtonGame(text): diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift index 09f9611cd87..e07e11baed8 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift @@ -181,12 +181,50 @@ public enum ReplyMarkupButtonRequestPeerType: Codable, Equatable { } public enum ReplyMarkupButtonAction: PostboxCoding, Equatable { + public struct PeerTypes: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public init() { + self.rawValue = 0 + } + + public static let users = PeerTypes(rawValue: 1 << 0) + public static let bots = PeerTypes(rawValue: 1 << 1) + public static let channels = PeerTypes(rawValue: 1 << 2) + public static let groups = PeerTypes(rawValue: 1 << 3) + + public var requestPeerTypes: [ReplyMarkupButtonRequestPeerType]? { + if self.isEmpty { + return nil + } + + var types: [ReplyMarkupButtonRequestPeerType] = [] + if self.contains(.users) { + types.append(.user(.init(isBot: false, isPremium: nil))) + } + if self.contains(.bots) { + types.append(.user(.init(isBot: true, isPremium: nil))) + } + if self.contains(.channels) { + types.append(.channel(.init(isCreator: false, hasUsername: nil, userAdminRights: nil, botAdminRights: nil))) + } + if self.contains(.groups) { + types.append(.group(.init(isCreator: false, hasUsername: nil, isForum: nil, botParticipant: false, userAdminRights: nil, botAdminRights: nil))) + } + return types + } + } + case text case url(String) case callback(requiresPassword: Bool, data: MemoryBuffer) case requestPhone case requestMap - case switchInline(samePeer: Bool, query: String) + case switchInline(samePeer: Bool, query: String, peerTypes: PeerTypes) case openWebApp case payment case urlAuth(url: String, buttonId: Int32) @@ -208,7 +246,7 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable { case 4: self = .requestMap case 5: - self = .switchInline(samePeer: decoder.decodeInt32ForKey("s", orElse: 0) != 0, query: decoder.decodeStringForKey("q", orElse: "")) + self = .switchInline(samePeer: decoder.decodeInt32ForKey("s", orElse: 0) != 0, query: decoder.decodeStringForKey("q", orElse: ""), peerTypes: PeerTypes(rawValue: decoder.decodeInt32ForKey("pt", orElse: 0))) case 6: self = .openWebApp case 7: @@ -243,10 +281,11 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable { encoder.encodeInt32(3, forKey: "v") case .requestMap: encoder.encodeInt32(4, forKey: "v") - case let .switchInline(samePeer, query): + case let .switchInline(samePeer, query, peerTypes): encoder.encodeInt32(5, forKey: "v") encoder.encodeInt32(samePeer ? 1 : 0, forKey: "s") encoder.encodeString(query, forKey: "q") + encoder.encodeInt32(peerTypes.rawValue, forKey: "pt") case .openWebApp: encoder.encodeInt32(6, forKey: "v") case .payment: diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 9f323f51dfa..54c7de93915 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -105,7 +105,7 @@ public final class ChatControllerInteraction { public let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool public let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void public let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void - public let activateSwitchInline: (PeerId?, String) -> Void + public let activateSwitchInline: (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void public let openUrl: (String, Bool, Bool?, Message?) -> Void public let shareCurrentLocation: () -> Void public let shareAccountContact: () -> Void @@ -217,7 +217,7 @@ public final class ChatControllerInteraction { sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void, - activateSwitchInline: @escaping (PeerId?, String) -> Void, + activateSwitchInline: @escaping (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, diff --git a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift index f646ced7c31..0c71d4b08f2 100644 --- a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift @@ -391,7 +391,7 @@ final class ChatButtonKeyboardInputNode: ChatInputNode { if let message = self.message { self.controllerInteraction.requestMessageActionCallback(message.id, data, false, requiresPassword) } - case let .switchInline(samePeer, query): + case let .switchInline(samePeer, query, _): if let message = message { var botPeer: Peer? diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 51186791a95..6bf258998de 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2512,7 +2512,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } })) } - }, activateSwitchInline: { [weak self] peerId, inputString in + }, activateSwitchInline: { [weak self] peerId, inputString, peerTypes in guard let strongSelf = self else { return } @@ -2536,7 +2536,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) } else { - strongSelf.openPeer(peer: nil, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil) + strongSelf.openPeer(peer: nil, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil, peerTypes: peerTypes) } } }, openUrl: { [weak self] url, concealed, _, message in @@ -4226,12 +4226,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { completion() controller?.dismiss() - strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)") + strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil) } } strongSelf.push(controller) } else { - strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)") + strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil) } } }, getNavigationController: { [weak self] in @@ -12873,12 +12873,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { completion() controller?.dismiss() - strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)") + strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil) } } strongSelf.push(controller) } else { - strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)") + strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil) } } }, completion: { [weak self] in @@ -16716,7 +16716,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - private func openPeer(peer: EnginePeer?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: MessageId? = nil, expandAvatar: Bool = false) { + private func openPeer(peer: EnginePeer?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: MessageId? = nil, expandAvatar: Bool = false, peerTypes: ReplyMarkupButtonAction.PeerTypes? = nil) { let _ = self.presentVoiceMessageDiscardAlert(action: { if case let .peer(currentPeerId) = self.chatLocation, peer?.id == currentPeerId { switch navigation { @@ -16815,7 +16815,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G break case let .chat(textInputState, _, _): if let textInputState = textInputState { - let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, selectForumThreads: true)) + let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, requestPeerType: peerTypes.flatMap { $0.requestPeerTypes }, selectForumThreads: true)) controller.peerSelected = { [weak self, weak controller] peer, threadId in let peerId = peer.id diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index e1a36e86093..5555d4e4403 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -860,7 +860,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true, false) case let .callback(requiresPassword, data): item.controllerInteraction.requestMessageActionCallback(item.message.id, data, false, requiresPassword) - case let .switchInline(samePeer, query): + case let .switchInline(samePeer, query, peerTypes): var botPeer: Peer? var found = false @@ -881,7 +881,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol peerId = item.message.id.peerId } if let botPeer = botPeer, let addressName = botPeer.addressName { - item.controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)") + item.controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes) } case .payment: item.controllerInteraction.openCheckoutOrReceipt(item.message.id) diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 9ace618b465..a4be07755f3 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -863,7 +863,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { controllerInteraction.requestMessageActionCallback(message.id, nil, true, false) case let .callback(requiresPassword, data): controllerInteraction.requestMessageActionCallback(message.id, data, false, requiresPassword) - case let .switchInline(samePeer, query): + case let .switchInline(samePeer, query, peerTypes): var botPeer: Peer? var found = false @@ -884,7 +884,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { peerId = message.id.peerId } if let botPeer = botPeer, let addressName = botPeer.addressName { - controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)") + controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes) } case .payment: controllerInteraction.openCheckoutOrReceipt(message.id) diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 18ac6833372..cf5a2c41503 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -274,7 +274,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, navigateToThreadMessage: { _, _, _ in - }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in + }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url, _, _, _ in self?.openUrl(url) }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 9ddce665ebd..5c295155b39 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -97,7 +97,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in - }, activateSwitchInline: { _, _ in + }, activateSwitchInline: { _, _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: { }, shareAccountContact: { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index afc7f249cf2..048961a0af8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -2678,7 +2678,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in - }, activateSwitchInline: { _, _ in + }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url, concealed, external, _ in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 43d8e09c8ee..3c756c16bba 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1457,7 +1457,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { clickThroughMessage?() }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false - }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, presentControllerInCurrent: { _, _ in }, navigationController: { From 6f6d80c7d46d2de52ef562af37c8994d6a1fb2d9 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 10 Apr 2023 21:29:27 +0400 Subject: [PATCH 34/57] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2e8da75f61..2728bbd0fd6 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Install Xcode (directly from https://developer.apple.com/download/applications o openssl rand -hex 8 ``` 2. Create a new Xcode project. Use `Telegram` as the Product Name. Use `org.{identifier from step 1}` as the Organization Identifier. -3. In Signing & Capabilities click on the `i` next to `Xcode Managed Profile` and copy the team ID (`ABCDEFG123`). +3. Open `Keychain Access` and navigate to `Certificates`. Locate `Apple Development: your@email.address (XXXXXXXXXX)` and double tap the certificate. Under `Details`, locate `Organizational Unit`. This is the Team ID. 4. Edit `build-system/template_minimal_development_configuration.json`. Use data from the previous steps. ## Generate an Xcode project From e99b4dd7e5c3109c067a93981e2e06f45f815ca4 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 10 Apr 2023 22:29:33 +0400 Subject: [PATCH 35/57] Update localization --- .../Telegram-iOS/en.lproj/Localizable.strings | 150 +++++++++++++++ .../Sources/ChatListController.swift | 15 +- .../Sources/ChatListControllerNode.swift | 8 +- .../ChatListFilterPresetController.swift | 73 +++---- .../ChatListFilterPresetListController.swift | 6 +- .../Node/ChatListStorageInfoItem.swift | 3 +- .../FolderInviteLinkListController.swift | 81 ++++---- .../Sources/InviteLinkViewController.swift | 3 +- .../ItemListFolderInviteLinkListItem.swift | 10 +- .../QrCodeUI/Sources/QrCodeScreen.swift | 5 +- .../ChatFolderLinkHeaderComponent.swift | 5 +- .../Sources/ChatFolderLinkPreviewScreen.swift | 178 ++++++++---------- .../Sources/PeerListItemComponent.swift | 9 +- 13 files changed, 306 insertions(+), 240 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0f35cb28f3f..5546b4bb27b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9166,3 +9166,153 @@ Sorry for the inconvenience."; "WallpaperPreview.PreviewInDayMode" = "Preview this background in day mode."; "Conversation.Theme.ApplyBackground" = "Set as Background"; + +"ChatList.ContextMuteAll" = "Mute All"; +"ChatList.ContextUnmuteAll" = "Unmute All"; +"ChatList.ToastFolderMuted" = "All chats in **%@** are now muted"; +"ChatList.ToastFolderUnmuted" = "All chats in **%@** are now unmuted"; +"ChatList.ContextMenuShare" = "Share"; +"ChatList.ContextMenuBadgeNew" = "NEW"; +"ChatList.AlertDeleteFolderTitle" = "Delete Folder"; +"ChatList.AlertDeleteFolderText" = "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder."; +"ChatList.PanelNewChatsAvailable_1" = "1 New Chat Available"; +"ChatList.PanelNewChatsAvailable_any" = "%d New Chats Available"; + +"ChatListFilter.SectionShare" = "SHARE FOLDER"; +"ChatListFilter.CreateLink" = "Create a new Link"; +"ChatListFilter.CreateLinkNew" = "Create an Invite Link"; +"ChatListFilter.ExcludeChatsAction" = "Add Chats to Exclude"; +"ChatListFilter.LinkListInfo" = "Create more links to set up different access levels for different people."; +"ChatListFilter.LinkListInfoNew" = "Share access to some of this folder's groups and channels with others."; +"ChatListFilter.ToastChatsAddedTitle_1" = "Сhat added to folder"; +"ChatListFilter.ToastChatsAddedTitle_any" = "%d chats added to folder"; +"ChatListFilter.ToastChatsAddedText" = "It will not affect chatlist of the links of this folder"; +"ChatListFilter.ToastChatsRemovedTitle_1" = "Сhat removed from folder"; +"ChatListFilter.ToastChatsRemovedTitle_any" = "%d chats added to folder"; +"ChatListFilter.ToastChatsRemovedText" = "It will not affect chatlist of the links of this folder"; +"ChatListFilter.AlertCreateFolderBeforeSharingText" = "Please finish creating this folder to share it."; +"ChatListFilter.ErrorShareInvalidFolder" = "You can’t share folders which have chat types or excluded chats."; +"ChatListFilter.SaveAlertActionSave" = "Save"; +"ChatListFilter.CreateLinkUnknownError" = "Error creating a share link"; +"ChatListFilter.CreateLinkErrorSomeoneHasChannelLimit" = "One of the groups in this folder can’t be added because one of its admins has too many groups and channels."; + +"ChatListFilterList.CreateFolder" = "Create a Folder"; + +"FolderLinkScreen.LabelCanInvite" = "you can invite others here"; + +"FolderLinkScreen.LabelUnavailableBot" = "you can't share chats with bots"; +"FolderLinkScreen.AlertTextUnavailableBot" = "You can't share chats with bots"; + +"FolderLinkScreen.LabelUnavailableUser" = "you can't share private chats"; +"FolderLinkScreen.AlertTextUnavailableUser" = "You can't share private chats"; + +"FolderLinkScreen.LabelUnavailableGeneric" = "you can't invite others here"; +"FolderLinkScreen.AlertTextUnavailablePrivateGroup" = "You don't have the admin rights to share invite links to this private group."; +"FolderLinkScreen.AlertTextUnavailablePublicGroup" = "You don't have the admin rights to share invite links to this group chat."; +"FolderLinkScreen.AlertTextUnavailablePrivateChannel" = "You don't have the admin rights to share invite links to this private channel."; +"FolderLinkScreen.AlertTextUnavailablePublicChannel" = "You don't have the admin rights to share invite links to this channel."; + +"FolderLinkScreen.TitleDescriptionUnavailable" = "You can only share groups and channels in which you are allowed to create invite links."; +"FolderLinkScreen.ChatCountHeaderUnavailable" = "There are no chats in this folder that you can share with others."; +"FolderLinkScreen.ChatsSectionHeaderUnavailable" = "THESE CHATS CANNOT BE SHARED"; + +"FolderLinkScreen.TitleDescriptionDeselected" = "Anyone with this link can add **%@** folder and the chats selected below."; +"FolderLinkScreen.ChatsSectionHeader" = "CHATS"; +"FolderLinkScreen.ChatsSectionHeaderActionSelectAll" = "SELECT ALL"; +"FolderLinkScreen.ChatsSectionHeaderActionDeselectAll" = "DESELECT ALL"; + +"FolderLinkScreen.TitleDescriptionSelectedCount_1" = "the 1 chat"; +"FolderLinkScreen.TitleDescriptionSelectedCount_any" = "the %d chats"; +"FolderLinkScreen.TitleDescriptionSelected" = "Anyone with this link can add **%1$@** folder and %2$@ selected below."; + +"FolderLinkScreen.ChatsSectionHeaderSelected_1" = "1 CHAT SELECTED"; +"FolderLinkScreen.ChatsSectionHeaderSelected_any" = "%d CHATS SELECTED"; + +"FolderLinkScreen.LinkSectionHeader" = "INVITE LINK"; + +"FolderLinkScreen.ContextActionNameLink" = "Name Link"; +"FolderLinkScreen.NameLink.Title" = "Name This Link"; + +"FolderLinkScreen.ToastNewChatAdded" = "People who already used the invite link will be able to join newly added chats."; +"FolderLinkScreen.SaveUnknownError" = "An error occurred while updating the link"; +"FolderLinkScreen.ToastLinkUpdated" = "Link updated"; + +"FolderLinkScreen.Title" = "Share Folder"; + +"FolderLinkScreen.SaveAlertTitle" = "Unsaved Changes"; +"FolderLinkScreen.SaveAlertText" = "You have changed the settings of this folder. Apply changes?"; +"FolderLinkScreen.SaveAlertActionDiscard" = "Discard"; +"FolderLinkScreen.SaveAlertActionApply" = "Apply"; +"FolderLinkScreen.SaveAlertActionContinue" = "Cancel"; + +"FolderLinkScreen.LinkActionCopy" = "Copy"; +"FolderLinkScreen.LinkActionShare" = "Share"; + +"InviteLink.LabelJoinedViaFolder" = "joined via a folder invite link"; + +"ChatListFilter.LinkLabelChatCount_1" = "includes 1 chat"; +"ChatListFilter.LinkLabelChatCount_any" = "includes %d chats"; +"ChatListFilter.LinkActionDelete" = "Delete"; + +"InviteLink.QRCodeFolder.Title" = "Invite by QR Code"; +"InviteLink.QRCodeFolder.Text" = "Everyone on Telegram can scan this code to add this folder and join the chats included in this invite link."; + +"FolderLinkPreview.IconTabLeft" = "All Chats"; +"FolderLinkPreview.IconTabRight" = "Personal"; +"FolderLinkPreview.TitleShare" = "Share Folder"; +"FolderLinkPreview.TitleRemove" = "Remove Folder"; +"FolderLinkPreview.TitleAddFolder" = "Add Folder"; +"FolderLinkPreview.TitleAddChats_1" = "Add %d Chat"; +"FolderLinkPreview.TitleAddChats_any" = "Add %d Chats"; +"FolderLinkPreview.LinkSectionHeader" = "INVITE LINKS"; +"FolderLinkPreview.RemoveSectionSelectedHeader_1" = "%d CHAT TO QUIT"; +"FolderLinkPreview.RemoveSectionSelectedHeader_any" = "%d CHATS TO QUIT"; +"FolderLinkPreview.ChatSectionHeader_1" = "1 CHAT IN THIS FOLDER"; +"FolderLinkPreview.ChatSectionHeader_any" = "%d CHATS IN THIS FOLDER"; +"FolderLinkPreview.ChatSectionJoinHeader_1" = "1 CHAT IN THIS FOLDER TO JOIN"; +"FolderLinkPreview.ChatSectionJoinHeader_any" = "%d CHATS IN THIS FOLDER TO JOIN"; + +"FolderLinkPreview.ToastChatsAddedTitle" = "Folder %@ Updated"; +"FolderLinkPreview.ToastChatsAddedText_1" = "You have joined %d new chat"; +"FolderLinkPreview.ToastChatsAddedText_any" = "You have joined %d new chats"; + +"FolderLinkPreview.ToastFolderAddedTitle" = "Folder %@ Added"; +"FolderLinkPreview.ToastFolderAddedText_1" = "You also joined %d chat"; +"FolderLinkPreview.ToastFolderAddedText_any" = "You also joined %d chats"; + +"FolderLinkPreview.TextLinkList" = "Create more links to set up different access\nlevels for different people."; +"FolderLinkPreview.TextRemoveFolder" = "Do you also want to quit the chats included in this folder?"; +"FolderLinkPreview.TextAllAdded" = "You have already added this\nfolder and its chats."; +"FolderLinkPreview.TextAddFolder" = "Do you want to add a new chat folder\nand join its groups and channels?"; + +"FolderLinkPreview.TextAddChatsCount_1" = "%d chat"; +"FolderLinkPreview.TextAddChatsCount_any" = "%d chats"; +"FolderLinkPreview.TextAddChats" = "Do you want to add **%1$@** to the\nfolder **%2$@**?"; + +"FolderLinkPreview.LabelPeerSubscriber" = "You are already a subscriber"; +"FolderLinkPreview.LabelPeerSubscribers_1" = "%d subscriber"; +"FolderLinkPreview.LabelPeerSubscribers_any" = "%d subscribers"; + +"FolderLinkPreview.LabelPeerMember" = "You are already a member"; +"FolderLinkPreview.LabelPeerMembers_1" = "%d member"; +"FolderLinkPreview.LabelPeerMembers_any" = "%d members"; + +"FolderLinkPreview.ToastAlreadyMemberChannel" = "You are already subscribed to this channel."; +"FolderLinkPreview.ToastAlreadyMemberGroup" = "You are already a member of this group."; + +"FolderLinkPreview.ButtonRemoveFolder" = "Remove Folder"; +"FolderLinkPreview.ButtonRemoveFolderAndChats" = "Remove Folder and Chats"; + +"FolderLinkPreview.ButtonDoNotJoinChats" = "Do Not Join Any Chats"; +"FolderLinkPreview.ButtonJoinChats" = "Join Chats"; +"FolderLinkPreview.ButtonAddFolder" = "Add Folder"; + +"FolderLinkPreview.ToastLeftTitle" = "Folder %@ deleted"; +"FolderLinkPreview.ToastLeftChatsText_1" = "You also left **%d** chat"; +"FolderLinkPreview.ToastLeftChatsText_any" = "You also left **%d** chats"; + +"FolderLinkPreview.ListSelectionSelectAllDynamicPartSelect" = "SELECT"; +"FolderLinkPreview.ListSelectionSelectAllDynamicPartDeselect" = "DESELECT"; +"FolderLinkPreview.ListSelectionSelectAllStaticPartSelect" = "ALL"; +"FolderLinkPreview.ListSelectionSelectAllStaticPartDeselect" = "ALL"; +"FolderLinkPreview.ListSelectionSelectAllFormat" = "{dynamic}{static}"; diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 5bba1511fe0..0f02abfe96c 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1599,12 +1599,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }))) } - //TODO:localize - for filter in filters { if filter.id == filterId, case let .filter(_, title, _, data) = filter { if let filterPeersAreMuted { - items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? "Unmute All" : "Mute All", textColor: .primary, badge: nil, icon: { theme in + items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? strongSelf.presentationData.strings.ChatList_ContextUnmuteAll : strongSelf.presentationData.strings.ChatList_ContextMuteAll, textColor: .primary, badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: filterPeersAreMuted.areMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { c, f in c.dismiss(completion: { @@ -1623,21 +1621,23 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let iconColor: UIColor = .white let overlayController: UndoOverlayController if !filterPeersAreMuted.areMuted { + let text = strongSelf.presentationData.strings.ChatList_ToastFolderMuted(title).string overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ "Middle.Group 1.Fill 1": iconColor, "Top.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor, "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: "All chats in **\(title)** are now muted", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) + ], title: nil, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) } else { + let text = strongSelf.presentationData.strings.ChatList_ToastFolderUnmuted(title).string overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ "Middle.Group 1.Fill 1": iconColor, "Top.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor, "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: "All chats in **\(title)** are now unmuted", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) + ], title: nil, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) } strongSelf.present(overlayController, in: .current) }) @@ -1645,7 +1645,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if !data.includePeers.peers.isEmpty && data.categories.isEmpty && !data.excludeRead && !data.excludeMuted && !data.excludeArchived && data.excludePeers.isEmpty { - items.append(.action(ContextMenuActionItem(text: "Share", textColor: .primary, badge: data.hasSharedLinks ? nil : ContextMenuActionBadge(value: "NEW", color: .accent, style: .label), icon: { theme in + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ContextMenuShare, textColor: .primary, badge: data.hasSharedLinks ? nil : ContextMenuActionBadge(value: strongSelf.presentationData.strings.ChatList_ContextMenuBadgeNew, color: .accent, style: .label), icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { c, f in c.dismiss(completion: { @@ -2997,8 +2997,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if hasLinks { - //TODO:localize - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [ + self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [ TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { confirmDeleteFolder() }), diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 261e946cf48..e05badfeb42 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -571,13 +571,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { self.topPanel = topPanel } - //TODO:localize - let title: String - if chatFolderUpdates.availableChatsToJoin == 1 { - title = "1 New Chat Available" - } else { - title = "\(chatFolderUpdates.availableChatsToJoin) New Chats Available" - } + let title: String = self.presentationData.strings.ChatList_PanelNewChatsAvailable(Int32(chatFolderUpdates.availableChatsToJoin)) let topPanelHeight: CGFloat = 44.0 diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 3d4d21fdbb3..0b888606a1d 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -547,11 +547,9 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { arguments.expandSection(.exclude) }) case let .inviteLinkHeader(hasLinks): - //TODO:localize - return ItemListSectionHeaderItem(presentationData: presentationData, text: "SHARE FOLDER", badge: hasLinks ? nil : "NEW", sectionId: self.section) + return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ChatListFilter_SectionShare, badge: hasLinks ? nil : presentationData.strings.ChatList_ContextMenuBadgeNew, sectionId: self.section) case let .inviteLinkCreate(hasLinks): - //TODO:localize - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? "Create a new Link" : "Create an Invite Link", sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? presentationData.strings.ChatListFilter_CreateLink : presentationData.strings.ChatListFilter_CreateLinkNew, sectionId: self.section, editing: false, action: { arguments.createLink() }) case let .inviteLink(_, link): @@ -648,8 +646,7 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio if let currentPreset, let data = currentPreset.data, data.isShared { } else { entries.append(.excludePeersHeader(presentationData.strings.ChatListFolder_ExcludedSectionHeader)) - //TODO:localize - entries.append(.addExcludePeer(title: "Add Chats to Exclude")) + entries.append(.addExcludePeer(title: presentationData.strings.ChatListFilter_ExcludeChatsAction)) var excludeCategoryIndex = 0 for category in ChatListFilterExcludeCategory.allCases { @@ -705,8 +702,7 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio } } - //TODO:localize - entries.append(.inviteLinkInfo(text: hasLinks ? "Create more links to set up different access levels for different people." : "Share access to some of this folder's groups and channels with others.")) + entries.append(.inviteLinkInfo(text: hasLinks ? presentationData.strings.ChatListFilter_LinkListInfo : presentationData.strings.ChatListFilter_LinkListInfoNew)) return entries } @@ -847,30 +843,13 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f removedPeers = data.includePeers.peers.filter({ !includePeers.contains($0) }) } if newPeers.count != 0 { - let title: String - let text: String - - //TODO:localize - if newPeers.count == 1 { - title = "Сhat added to folder" - text = "It will not affect chatlist of the links of this folder" - } else { - title = "\(newPeers.count) chats added to folder" - text = "It will not affect chatlist of the links of this folder" - } + let title: String = presentationData.strings.ChatListFilter_ToastChatsAddedTitle(Int32(newPeers.count)) + let text: String = presentationData.strings.ChatListFilter_ToastChatsAddedText presentUndo(.universal(animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil)) } else if removedPeers.count != 0 { - let title: String - let text: String - - if newPeers.count == 1 { - title = "Сhat removed from folder" - text = "It will not affect chatlist of the links of this folder" - } else { - title = "\(newPeers.count) chats removed from folder" - text = "It will not affect chatlist of the links of this folder" - } + let title: String = presentationData.strings.ChatListFilter_ToastChatsRemovedTitle(Int32(newPeers.count)) + let text: String = presentationData.strings.ChatListFilter_ToastChatsRemovedText presentUndo(.universal(animation: "anim_remove_from_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil)) } @@ -1327,14 +1306,13 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi let _ = (updatedCurrentPreset |> take(1) |> deliverOnMainQueue).start(next: { currentPreset in if let currentPreset, let data = currentPreset.data, data.hasSharedLinks { - //TODO:localize let title: String let text: String let presentationData = context.sharedContext.currentPresentationData.with { $0 } - title = "Сhat removed from folder" - text = "It will not affect chatlist of the links of this folder" + title = presentationData.strings.ChatListFilter_ToastChatsRemovedTitle(1) + text = presentationData.strings.ChatListFilter_ToastChatsRemovedText presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_remove_from_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) } @@ -1394,17 +1372,15 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi }, createLink: { if initialPreset == nil { - //TODO:localize let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let text = "Please finish creating this folder to share it." + let text = presentationData.strings.ChatListFilter_AlertCreateFolderBeforeSharingText presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) } else { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let state = stateValue.with({ $0 }) if state.additionallyIncludePeers.isEmpty { - //TODO:localize - let text = "You can’t share folders which have chat types or excluded chats." + let text = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) return @@ -1418,14 +1394,13 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi let _ = (updatedCurrentPreset |> take(1) |> deliverOnMainQueue).start(next: { currentPreset in if let currentPreset, let data = currentPreset.data { - //TODO:localize var unavailableText: String? if !data.categories.isEmpty { - unavailableText = "You can’t share folders which have chat types or excluded chats." + unavailableText = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder } else if data.excludeArchived || data.excludeRead || data.excludeMuted { - unavailableText = "You can’t share folders which have chat types or excluded chats." + unavailableText = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder } else if !data.excludePeers.isEmpty { - unavailableText = "You can’t share folders which have chat types or excluded chats." + unavailableText = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder } if let unavailableText { statusController?.dismiss() @@ -1602,14 +1577,13 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi let _ = (updatedCurrentPreset |> take(1) |> deliverOnMainQueue).start(next: { currentPreset in if let currentPreset, let data = currentPreset.data, data.hasSharedLinks { - //TODO:localize let title: String let text: String let presentationData = context.sharedContext.currentPresentationData.with { $0 } - title = "Сhat removed from folder" - text = "It will not affect chatlist of the links of this folder" + title = presentationData.strings.ChatListFilter_ToastChatsRemovedTitle(1) + text = presentationData.strings.ChatListFilter_ToastChatsRemovedText presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_remove_from_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) } @@ -1790,8 +1764,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi TextAlertAction(type: .genericAction, title: presentationData.strings.ChatListFolder_DiscardDiscard, action: { dismissImpl?() }), - //TODO:localize - TextAlertAction(type: .defaultAction, title: "Save", action: { + TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatListFilter_SaveAlertActionSave, action: { applyImpl?(false, { dismissImpl?() }) @@ -1932,11 +1905,13 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec })) }, error: { error in completed() - //TODO:localize + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let text: String switch error { case .generic: - text = "An error occurred" + text = presentationData.strings.ChatListFilter_CreateLinkUnknownError case let .sharedFolderLimitExceeded(limit, _): let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .membershipInSharedFolders, count: limit, action: { pushPremiumController(PremiumIntroScreen(context: context, source: .membershipInSharedFolders)) @@ -1966,10 +1941,8 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec return case .someUserTooManyChannels: - //TODO:localize - text = "One of the groups in this folder can’t be added because one of its admins has too many groups and channels." + text = presentationData.strings.ChatListFilter_CreateLinkErrorSomeoneHasChannelLimit } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } presentController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) }) } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 460e2b37226..6bcb8e89540 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -223,8 +223,7 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present entries.append(.listHeader(presentationData.strings.ChatListFolderSettings_FoldersSection)) - //TODO:localize - entries.append(.addItem(text: "Create a Folder", isEditing: state.isEditing)) + entries.append(.addItem(text: presentationData.strings.ChatListFilterList_CreateFolder, isEditing: state.isEditing)) if !filters.isEmpty || suggestedFilters.isEmpty { var folderCount = 0 @@ -450,8 +449,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch } if hasLinks { - //TODO:localize - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [ + presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [ TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { confirmDeleteFolder() }), diff --git a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift index be5aef54808..7e188fd8cab 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift @@ -221,8 +221,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { switch item.notice { case .chatFolderUpdates: - //TODO:locallize - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: "Hide", icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.ChatList_HideAction, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) default: strongSelf.setRevealOptions((left: [], right: [])) } diff --git a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift index 4d4a17c61b4..ad43b752f72 100644 --- a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift @@ -170,7 +170,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry { case let .mainLinkHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .mainLink(link, isGenerating): - return ItemListFolderInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: link, count: 0, peers: [], displayButton: true, enableButton: !isGenerating, buttonTitle: link != nil ? "Copy" : "Generate Invite Link", secondaryButtonTitle: link != nil ? "Share" : nil, displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { + return ItemListFolderInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: link, count: 0, peers: [], displayButton: true, enableButton: !isGenerating, buttonTitle: presentationData.strings.FolderLinkScreen_LinkActionCopy, secondaryButtonTitle: link != nil ? presentationData.strings.FolderLinkScreen_LinkActionShare : nil, displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { if let link { arguments.copyLink(link.link) } @@ -196,7 +196,6 @@ private enum InviteLinksListEntry: ItemListNodeEntry { case let .peersInfo(text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .peer(_, peer, isSelected, disabledReasonText): - //TODO:localize return ItemListPeerItem( presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), @@ -204,7 +203,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry { context: arguments.context, peer: peer, presence: nil, - text: .text(disabledReasonText ?? "you can invite others here", .secondary), + text: .text(disabledReasonText ?? presentationData.strings.FolderLinkScreen_LabelCanInvite, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: isSelected, style: .leftCheck, isEnabled: disabledReasonText == nil), @@ -232,8 +231,6 @@ private func folderInviteLinkListControllerEntries( ) -> [InviteLinksListEntry] { var entries: [InviteLinksListEntry] = [] - //TODO:localize - var infoString: String? let chatCountString: String let peersHeaderString: String @@ -244,34 +241,26 @@ private func folderInviteLinkListControllerEntries( var selectAllString: String? if !canShareChats { - infoString = "You can only share groups and channels in which you are allowed to create invite links." - chatCountString = "There are no chats in this folder that you can share with others." - peersHeaderString = "THESE CHATS CANNOT BE SHARED" + infoString = presentationData.strings.FolderLinkScreen_TitleDescriptionUnavailable + chatCountString = presentationData.strings.FolderLinkScreen_ChatCountHeaderUnavailable + peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderUnavailable } else if state.selectedPeerIds.isEmpty { - chatCountString = "Anyone with this link can add **\(title)** folder and the chats selected below." - peersHeaderString = "CHATS" - if allPeers.count > 1 { - selectAllString = allSelected ? "DESELECT ALL" : "SELECT ALL" - } - } else if state.selectedPeerIds.count == 1 { - chatCountString = "Anyone with this link can add **\(title)** folder and the 1 chat selected below." - peersHeaderString = "1 CHAT SELECTED" + chatCountString = presentationData.strings.FolderLinkScreen_TitleDescriptionDeselected(title).string + peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeader if allPeers.count > 1 { - selectAllString = allSelected ? "DESELECT ALL" : "SELECT ALL" + selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll } } else { - chatCountString = "Anyone with this link can add **\(title)** folder and the \(state.selectedPeerIds.count) chats selected below." - peersHeaderString = "\(state.selectedPeerIds.count) CHATS SELECTED" + chatCountString = presentationData.strings.FolderLinkScreen_TitleDescriptionSelected(title, presentationData.strings.FolderLinkScreen_TitleDescriptionSelectedCount(Int32(state.selectedPeerIds.count))).string + peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderSelected(Int32(state.selectedPeerIds.count)) if allPeers.count > 1 { - selectAllString = allSelected ? "DESELECT ALL" : "SELECT ALL" + selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll } } entries.append(.header(chatCountString)) - //TODO:localize - if canShareChats { - entries.append(.mainLinkHeader("INVITE LINK")) + entries.append(.mainLinkHeader(presentationData.strings.FolderLinkScreen_LinkSectionHeader)) entries.append(.mainLink(link: state.currentLink, isGenerating: state.generatingLink)) } @@ -290,12 +279,12 @@ private func folderInviteLinkListControllerEntries( if !canShareLinkToPeer(peer: peer) { if case let .user(user) = peer { if user.botInfo != nil { - disabledReasonText = "you can't share chats with bots" + disabledReasonText = presentationData.strings.FolderLinkScreen_LabelUnavailableBot } else { - disabledReasonText = "you can't share private chats" + disabledReasonText = presentationData.strings.FolderLinkScreen_LabelUnavailableUser } } else { - disabledReasonText = "you can't invite others here" + disabledReasonText = presentationData.strings.FolderLinkScreen_LabelUnavailableGeneric } } entries.append(.peer(index: entries.count, peer: peer, isSelected: state.selectedPeerIds.contains(peer.id), disabledReasonText: disabledReasonText)) @@ -420,15 +409,14 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese let presentationData = context.sharedContext.currentPresentationData.with { $0 } var items: [ContextMenuItem] = [] - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Name Link", icon: { theme in + items.append(.action(ContextMenuActionItem(text: presentationData.strings.FolderLinkScreen_ContextActionNameLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pencil"), color: theme.contextMenu.primaryColor) }, action: { _, f in f(.dismissWithoutContent) let state = stateValue.with({ $0 }) - let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: "Name This Link", titleFont: .bold, value: state.title ?? "", characterLimit: 32, apply: { value in + let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: presentationData.strings.FolderLinkScreen_NameLink_Title, titleFont: .bold, value: state.title ?? "", characterLimit: 32, apply: { value in if let value { updateState { state in var state = state @@ -481,6 +469,8 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) presentInGlobalOverlayImpl?(contextController) }, peerAction: { peer, isEnabled in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + if isEnabled { var added = false updateState { state in @@ -503,17 +493,15 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese didDisplayAddPeerNotice = true dismissTooltipsImpl?() - //TODO:localize - displayTooltipImpl?(.info(title: nil, text: "People who already used the invite link will be able to join newly added chats.", timeout: 8), true) + displayTooltipImpl?(.info(title: nil, text: presentationData.strings.FolderLinkScreen_ToastNewChatAdded, timeout: 8), true) } } else { - //TODO:localize let text: String if case let .user(user) = peer { if user.botInfo != nil { - text = "You can't share chats with bots" + text = presentationData.strings.FolderLinkScreen_AlertTextUnavailableBot } else { - text = "You can't share private chats" + text = presentationData.strings.FolderLinkScreen_AlertTextUnavailableUser } } else { var isGroup = true @@ -523,15 +511,15 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese } if isGroup { if isPrivate { - text = "You don't have the admin rights to share invite links to this private group." + text = presentationData.strings.FolderLinkScreen_AlertTextUnavailablePrivateGroup } else { - text = "You don't have the admin rights to share invite links to this group chat." + text = presentationData.strings.FolderLinkScreen_AlertTextUnavailablePublicGroup } } else { if isPrivate { - text = "You don't have the admin rights to share invite links to this private channel." + text = presentationData.strings.FolderLinkScreen_AlertTextUnavailablePrivateChannel } else { - text = "You don't have the admin rights to share invite links to this channel." + text = presentationData.strings.FolderLinkScreen_AlertTextUnavailablePublicChannel } } } @@ -605,12 +593,11 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese dismissTooltipsImpl?() let presentationData = context.sharedContext.currentPresentationData.with { $0 } - //TODO:localize - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "An error occurred.", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.FolderLinkScreen_SaveUnknownError, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) }, completed: { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } linkUpdated(ExportedChatFolderLink(title: state.title ?? "", link: currentLink.link, peerIds: Array(state.selectedPeerIds), isRevoked: false)) - //TODO:localize - displayTooltipImpl?(.info(title: nil, text: "Link updated", timeout: 3), false) + displayTooltipImpl?(.info(title: nil, text: presentationData.strings.FolderLinkScreen_ToastLinkUpdated, timeout: 3), false) dismissImpl?() })) @@ -666,10 +653,9 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese animateChanges = true } - //TODO:localize let title: ItemListControllerTitle - var folderTitle = "Share Folder" + var folderTitle = presentationData.strings.FolderLinkScreen_Title if let title = state.title, !title.isEmpty { folderTitle = title } @@ -742,13 +728,12 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese if hasChanges { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - //TODO:localize - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Unsaved Changes", text: "You have changed the settings of this folder. Apply changes?", actions: [ - TextAlertAction(type: .genericAction, title: "Discard", action: { + presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.FolderLinkScreen_SaveAlertTitle, text: presentationData.strings.FolderLinkScreen_SaveAlertText, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.FolderLinkScreen_SaveAlertActionDiscard, action: { f() dismissImpl?() }), - TextAlertAction(type: .defaultAction, title: state.selectedPeerIds.isEmpty ? "Continue" : "Apply", action: { + TextAlertAction(type: .defaultAction, title: state.selectedPeerIds.isEmpty ? presentationData.strings.FolderLinkScreen_SaveAlertActionApply : presentationData.strings.FolderLinkScreen_SaveAlertActionContinue, action: { applyChangesImpl?() }) ]), nil) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index 794cb8df900..5596edb62bd 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -227,8 +227,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable { case let .importer(_, _, dateTimeFormat, peer, date, joinedViaFolderLink, loading): let dateString: String if joinedViaFolderLink { - //TODO:localize - dateString = "joined via a folder invite link" + dateString = presentationData.strings.InviteLink_LabelJoinedViaFolder } else { dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat) } diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift index 8173bb57a5b..7e75b08bbe9 100644 --- a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift @@ -308,12 +308,7 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode titleText = invite.title } - //TODO:localize - if invite.peerIds.count == 1 { - subtitleText = "includes 1 chat" - } else { - subtitleText = "includes \(invite.peerIds.count) chats" - } + subtitleText = item.presentationData.strings.ChatListFilter_LinkLabelChatCount(Int32(invite.peerIds.count)) } else { titleText = " " subtitleText = " " @@ -519,8 +514,7 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) if item.removeAction != nil { - //TODO:localize - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: "Delete", icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.ChatListFilter_LinkActionDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])) } else { strongSelf.setRevealOptions((left: [], right: [])) } diff --git a/submodules/QrCodeUI/Sources/QrCodeScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScreen.swift index 86330a046bc..08dff93dac7 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScreen.swift @@ -239,9 +239,8 @@ public final class QrCodeScreen: ViewController { title = self.presentationData.strings.InviteLink_QRCode_Title text = isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel case .chatFolder: - //TODO:localize - title = "Invite by QR Code" - text = "Everyone on Telegram can scan this code to add this folder and join the chats included in this invite link." + title = self.presentationData.strings.InviteLink_QRCodeFolder_Title + text = self.presentationData.strings.InviteLink_QRCodeFolder_Text default: title = "" text = "" diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkHeaderComponent.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkHeaderComponent.swift index c3f97c1ef4f..b58d9105de1 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkHeaderComponent.swift @@ -151,9 +151,8 @@ final class ChatFolderLinkHeaderComponent: Component { let badgeSpacing: CGFloat = 6.0 if themeUpdated { - //TODO:localize - let leftString = NSAttributedString(string: "All Chats", font: Font.semibold(14.0), textColor: component.theme.list.freeTextColor) - let rightString = NSAttributedString(string: "Personal", font: Font.semibold(14.0), textColor: component.theme.list.freeTextColor) + let leftString = NSAttributedString(string: component.strings.FolderLinkPreview_IconTabLeft, font: Font.semibold(14.0), textColor: component.theme.list.freeTextColor) + let rightString = NSAttributedString(string: component.strings.FolderLinkPreview_IconTabRight, font: Font.semibold(14.0), textColor: component.theme.list.freeTextColor) let leftStringBounds = leftString.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) let rightStringBounds = rightString.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift index 848ac5fe1e8..b91f2cbd838 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift @@ -388,12 +388,10 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { var allChatsAdded = false var canAddChatCount = 0 if case .linkList = component.subject { - //TODO:localize - titleString = "Share Folder" + titleString = environment.strings.FolderLinkPreview_TitleShare } else if let linkContents = component.linkContents { - //TODO:localize if case .remove = component.subject { - titleString = "Remove Folder" + titleString = environment.strings.FolderLinkPreview_TitleRemove } else if linkContents.localFilterId != nil { if linkContents.alreadyMemberPeerIds == Set(linkContents.peers.map(\.id)) { allChatsAdded = true @@ -401,14 +399,12 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { canAddChatCount = linkContents.peers.map(\.id).count - linkContents.alreadyMemberPeerIds.count if allChatsAdded { - titleString = "Add Folder" - } else if canAddChatCount == 1 { - titleString = "Add \(canAddChatCount) chat" + titleString = environment.strings.FolderLinkPreview_TitleAddFolder } else { - titleString = "Add \(canAddChatCount) chats" + titleString = environment.strings.FolderLinkPreview_TitleAddChats(Int32(canAddChatCount)) } } else { - titleString = "Add Folder" + titleString = environment.strings.FolderLinkPreview_TitleAddFolder } } else { titleString = " " @@ -464,26 +460,17 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { let text: String if case .linkList = component.subject { - text = "Create more links to set up different access\nlevels for different people." + text = environment.strings.FolderLinkPreview_TextLinkList } else if let linkContents = component.linkContents { if case .remove = component.subject { - text = "Do you also want to quit the chats included in this folder?" + text = environment.strings.FolderLinkPreview_TextRemoveFolder } else if allChatsAdded { - text = "You have already added this\nfolder and its chats." + text = environment.strings.FolderLinkPreview_TextAllAdded } else if linkContents.localFilterId == nil { - text = "Do you want to add a new chat folder\nand join its groups and channels?" + text = environment.strings.FolderLinkPreview_TextAddFolder } else { - let chatCountString: String - if canAddChatCount == 1 { - chatCountString = "1 chat" - } else { - chatCountString = "\(canAddChatCount) chats" - } - if let title = linkContents.title { - text = "Do you want to add **\(chatCountString)** to the\nfolder **\(title)**?" - } else { - text = "Do you want to add **\(chatCountString)** chats to the\nfolder?" - } + let chatCountString: String = environment.strings.FolderLinkPreview_TextAddChatsCount(Int32(canAddChatCount)) + text = environment.strings.FolderLinkPreview_TextAddChats(chatCountString, linkContents.title ?? "").string } } else { text = " " @@ -544,7 +531,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { theme: environment.theme, sideInset: 0.0, iconName: "Contact List/LinkActionIcon", - title: "Create a New Link", + title: environment.strings.InviteLink_Create, hasNext: !self.linkListItems.isEmpty, action: { [weak self] in self?.openCreateLink() @@ -582,12 +569,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { self.items[id] = item } - let subtitle: String - if link.peerIds.count == 1 { - subtitle = "includes 1 chat" - } else { - subtitle = "includes \(link.peerIds.count) chats" - } + let subtitle: String = environment.strings.ChatListFilter_LinkLabelChatCount(Int32(link.peerIds.count)) let itemComponent = LinkListItemComponent( theme: environment.theme, @@ -713,15 +695,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { var subtitle: String? if case let .channel(channel) = peer, case .broadcast = channel.info { if linkContents.alreadyMemberPeerIds.contains(peer.id) { - subtitle = "You are already a subscriber" + subtitle = environment.strings.FolderLinkPreview_LabelPeerSubscriber } else if let memberCount = linkContents.memberCounts[peer.id] { - subtitle = "\(memberCount) subscribers" + subtitle = environment.strings.FolderLinkPreview_LabelPeerSubscribers(Int32(memberCount)) } } else { if linkContents.alreadyMemberPeerIds.contains(peer.id) { - subtitle = "You are already a member" + subtitle = environment.strings.FolderLinkPreview_LabelPeerMember } else if let memberCount = linkContents.memberCounts[peer.id] { - subtitle = "\(memberCount) participants" + subtitle = environment.strings.FolderLinkPreview_LabelPeerMembers(Int32(memberCount)) } } @@ -753,9 +735,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let text: String if case let .channel(channel) = peer, case .broadcast = channel.info { - text = "You are already a member of this channel." + text = presentationData.strings.FolderLinkPreview_ToastAlreadyMemberChannel } else { - text = "You are already a member of this group." + text = presentationData.strings.FolderLinkPreview_ToastAlreadyMemberGroup } controller.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: component.context, peers: [peer], title: nil, text: text, customUndoText: nil), elevatedLayout: false, action: { _ in true }), in: .current) } else { @@ -798,43 +780,59 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { let listHeaderTitle: String if case .linkList = component.subject { - listHeaderTitle = "INVITE LINKS" + listHeaderTitle = environment.strings.FolderLinkPreview_LinkSectionHeader } else if let linkContents = component.linkContents { if case .remove = component.subject { - if linkContents.peers.count == 1 { - listHeaderTitle = "1 CHAT TO QUIT" - } else { - listHeaderTitle = "\(linkContents.peers.count) CHATS TO QUIT" - } + listHeaderTitle = environment.strings.FolderLinkPreview_RemoveSectionSelectedHeader(Int32(linkContents.peers.count)) } else if allChatsAdded { - if linkContents.peers.count == 1 { - listHeaderTitle = "1 CHAT IN THIS FOLDER" - } else { - listHeaderTitle = "\(linkContents.peers.count) CHATS IN THIS FOLDER" - } + listHeaderTitle = environment.strings.FolderLinkPreview_ChatSectionHeader(Int32(linkContents.peers.count)) } else { - if linkContents.peers.count == 1 { - listHeaderTitle = "1 CHAT IN FOLDER TO JOIN" - } else { - listHeaderTitle = "\(linkContents.peers.count) CHATS IN FOLDER TO JOIN" - } + listHeaderTitle = environment.strings.FolderLinkPreview_ChatSectionJoinHeader(Int32(linkContents.peers.count)) } } else { listHeaderTitle = " " } - //TODO:localize - let listHeaderActionItems: [AnimatedCounterComponent.Item] + var listHeaderActionItems: [AnimatedCounterComponent.Item] = [] + + let dynamicIndex = environment.strings.FolderLinkPreview_ListSelectionSelectAllFormat.range(of: "{dynamic}") + let staticIndex = environment.strings.FolderLinkPreview_ListSelectionSelectAllFormat.range(of: "{static}") + var headerActionItemIndices: [Int: Int] = [:] + if let dynamicIndex, let staticIndex { + if dynamicIndex.lowerBound < staticIndex.lowerBound { + headerActionItemIndices[0] = 0 + headerActionItemIndices[1] = 1 + } else { + headerActionItemIndices[0] = 1 + headerActionItemIndices[1] = 0 + } + } else if dynamicIndex != nil { + headerActionItemIndices[0] = 0 + } else if staticIndex != nil { + headerActionItemIndices[1] = 0 + } + + let dynamicItem: AnimatedCounterComponent.Item + let staticItem: AnimatedCounterComponent.Item + if self.selectedItems.count == self.items.count { - listHeaderActionItems = [ - AnimatedCounterComponent.Item(id: AnyHashable(0), text: "DESELECT", numericValue: 0), - AnimatedCounterComponent.Item(id: AnyHashable(1), text: "ALL", numericValue: 1) - ] + dynamicItem = AnimatedCounterComponent.Item(id: AnyHashable(0), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllDynamicPartDeselect, numericValue: 0) + staticItem = AnimatedCounterComponent.Item(id: AnyHashable(1), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllStaticPartDeselect, numericValue: 1) } else { - listHeaderActionItems = [ - AnimatedCounterComponent.Item(id: AnyHashable(0), text: "SELECT", numericValue: 1), - AnimatedCounterComponent.Item(id: AnyHashable(1), text: "ALL", numericValue: 1) - ] + dynamicItem = AnimatedCounterComponent.Item(id: AnyHashable(0), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllDynamicPartSelect, numericValue: 1) + staticItem = AnimatedCounterComponent.Item(id: AnyHashable(1), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllStaticPartSelect, numericValue: 1) + } + + if let dynamicIndex = headerActionItemIndices[0], let staticIndex = headerActionItemIndices[1] { + if dynamicIndex < staticIndex { + listHeaderActionItems = [dynamicItem, staticItem] + } else { + listHeaderActionItems = [staticItem, dynamicItem] + } + } else if headerActionItemIndices[0] != nil { + listHeaderActionItems = [dynamicItem] + } else if headerActionItemIndices[1] != nil { + listHeaderActionItems = [staticItem] } let listHeaderBody = MarkdownAttributeSet(font: Font.with(size: 13.0, design: .regular, traits: [.monospacedNumbers]), textColor: environment.theme.list.freeTextColor) @@ -928,23 +926,23 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { if case .remove = component.subject { actionButtonBadge = self.selectedItems.count if self.selectedItems.isEmpty { - actionButtonTitle = "Remove Folder" + actionButtonTitle = environment.strings.FolderLinkPreview_ButtonRemoveFolder } else { - actionButtonTitle = "Remove Folder and Chats" + actionButtonTitle = environment.strings.FolderLinkPreview_ButtonRemoveFolderAndChats } } else if allChatsAdded { actionButtonBadge = 0 - actionButtonTitle = "OK" + actionButtonTitle = environment.strings.Common_OK } else if let linkContents = component.linkContents { actionButtonBadge = max(0, self.selectedItems.count - (linkContents.peers.count - canAddChatCount)) if linkContents.localFilterId != nil { if self.selectedItems.isEmpty { - actionButtonTitle = "Do Not Join Any Chats" + actionButtonTitle = environment.strings.FolderLinkPreview_ButtonDoNotJoinChats } else { - actionButtonTitle = "Join Chats" + actionButtonTitle = environment.strings.FolderLinkPreview_ButtonJoinChats } } else { - actionButtonTitle = "Add Folder" + actionButtonTitle = environment.strings.FolderLinkPreview_ButtonAddFolder } } else { actionButtonTitle = " " @@ -987,17 +985,13 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { let folderTitle = linkContents.title ?? "" + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) + var additionalText: String? if !self.selectedItems.isEmpty { - if self.selectedItems.count == 1 { - additionalText = "You also left **1** chat" - } else { - additionalText = "You also left **\(self.selectedItems.count)** chats" - } + additionalText = presentationData.strings.FolderLinkPreview_ToastLeftChatsText(Int32(self.selectedItems.count)) } - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) - var chatListController: ChatListController? if let navigationController = controller.navigationController as? NavigationController { for viewController in navigationController.viewControllers.reversed() { @@ -1025,7 +1019,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { let selectedItems = self.selectedItems let undoOverlayController = UndoOverlayController( presentationData: presentationData, - content: .removedChat(title: "Folder \(folderTitle) deleted", text: additionalText), + content: .removedChat(title: presentationData.strings.FolderLinkPreview_ToastLeftTitle(folderTitle).string, text: additionalText), elevatedLayout: false, action: { value in if case .commit = value { @@ -1106,7 +1100,6 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { return } - //TODO:localize let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) var isUpdates = false @@ -1119,29 +1112,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { } if isUpdates { - let chatCountString: String - if result.newChatCount == 1 { - chatCountString = "1 new chat" - } else { - chatCountString = "\(result.newChatCount) new chats" - } - - chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: "Folder \(result.title) Updated", text: "You have joined \(chatCountString)", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) + chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitle(result.title).string, text: presentationData.strings.FolderLinkPreview_ToastChatsAddedText(Int32(result.newChatCount)), customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) } else if result.newChatCount != 0 { - let chatCountString: String - if result.newChatCount == 1 { - chatCountString = "1 chat" - } else { - chatCountString = "\(result.newChatCount) chats" - } - let animationBackgroundColor: UIColor if presentationData.theme.overallDarkAppearance { animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor } else { animationBackgroundColor = UIColor(rgb: 0x474747) } - chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: "Folder \(result.title) Added", text: "You also joined \(chatCountString)", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) + chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitle(result.title).string, text: presentationData.strings.FolderLinkPreview_ToastFolderAddedText(Int32(result.newChatCount)), customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) } else { let animationBackgroundColor: UIColor if presentationData.theme.overallDarkAppearance { @@ -1149,7 +1128,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { } else { animationBackgroundColor = UIColor(rgb: 0x474747) } - chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: "Folder \(result.title) Added", text: "", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) + chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitle(result.title).string, text: "", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) } }) } @@ -1423,11 +1402,12 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { let context = component.context let navigationController = controller.navigationController as? NavigationController - //TODO:localize + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let text: String switch error { case .generic: - text = "An error occurred" + text = presentationData.strings.ChatListFilter_CreateLinkUnknownError case let .sharedFolderLimitExceeded(limit, _): let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .membershipInSharedFolders, count: limit, action: { [weak navigationController] in guard let navigationController else { @@ -1472,10 +1452,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { return case .someUserTooManyChannels: - //TODO:localize - text = "One of the groups in this folder can’t be added because one of its admins has too many groups and channels." + text = presentationData.strings.ChatListFilter_CreateLinkErrorSomeoneHasChannelLimit } - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }) } diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift index 4f8e5a3ad98..d5acbc67b85 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift @@ -218,20 +218,19 @@ final class PeerListItemComponent: Component { } } - //TODO:localize let labelData: (String, Bool) if let subtitle = component.subtitle { labelData = (subtitle, false) } else if case .legacyGroup = component.peer { - labelData = ("group", false) + labelData = (component.strings.Group_Status, false) } else if case let .channel(channel) = component.peer { if case .group = channel.info { - labelData = ("group", false) + labelData = (component.strings.Group_Status, false) } else { - labelData = ("channel", false) + labelData = (component.strings.Channel_Status, false) } } else { - labelData = ("group", false) + labelData = (component.strings.Group_Status, false) } let labelSize = self.label.update( From 1f976ed89e0574d4d321c8b15a161152f535f030 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 10 Apr 2023 22:43:18 +0400 Subject: [PATCH 36/57] Fix build --- .../TelegramCore/Sources/PendingMessages/EnqueueMessage.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index e4da935a56f..176b04e7113 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -693,9 +693,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, buttons.append(button) } else if case .urlAuth = button.action { buttons.append(button) - } else if case let .switchInline(samePeer, query) = button.action, sourceSentViaBot { + } else if case let .switchInline(samePeer, query, peerTypes) = button.action, sourceSentViaBot { let samePeer = samePeer && peerId == sourceMessage.id.peerId - let updatedButton = ReplyMarkupButton(title: button.titleWhenForwarded ?? button.title, titleWhenForwarded: button.titleWhenForwarded, action: .switchInline(samePeer: samePeer, query: query)) + let updatedButton = ReplyMarkupButton(title: button.titleWhenForwarded ?? button.title, titleWhenForwarded: button.titleWhenForwarded, action: .switchInline(samePeer: samePeer, query: query, peerTypes: peerTypes)) buttons.append(updatedButton) } else { rows.removeAll() From 91945e22aa856d38db55452a7f901e825ca37296 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 10 Apr 2023 22:43:33 +0400 Subject: [PATCH 37/57] Add Xcode release configuration --- Telegram/BUILD | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Telegram/BUILD b/Telegram/BUILD index 8449b491b17..00a75be7dde 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -2009,6 +2009,16 @@ xcodeproj( ], target_environments = ["device", "simulator"], ), + xcode_configurations = { + "Debug": { + "//command_line_option:compilation_mode": "dbg", + }, + "Release": { + "//command_line_option:compilation_mode": "opt", + }, + }, + default_xcode_configuration = "Debug" + ) # Temporary targets used to simplify webrtc build tests From 4be2edbc7283c9402ff08a00bd684c6ca42bed43 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 11 Apr 2023 20:59:17 +0400 Subject: [PATCH 38/57] Fix chat request panel info display for bots --- .../TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index dd2d188c446..ea27f2b5f4f 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -101,6 +101,9 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat displayActionsPanel = true } } + if peerStatusSettings.requestChatTitle != nil { + displayActionsPanel = true + } } if displayActionsPanel && (selectedContext == nil || selectedContext! <= .pinnedMessage) { From 8a3acad78aea9157bfe5cb25f1f9447046a765fa Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 11 Apr 2023 21:52:22 +0400 Subject: [PATCH 39/57] Fix month display in media scrolling indicator --- .../Telegram-iOS/en.lproj/Localizable.strings | 37 ++++++++++--------- .../Sources/SparseItemGridScrollingArea.swift | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0f35cb28f3f..81612f43430 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -255,8 +255,8 @@ "PUSH_MESSAGE_SUGGEST_USERPIC" = "%1$@|suggested you new profile photo"; -"PUSH_MESSAGE_WALLPAPER" = "%1$@ set a new background for the chat with you"; -"PUSH_MESSAGE_SAME_WALLPAPER" = "%1$@ set the same background for the chat with you"; +"PUSH_MESSAGE_WALLPAPER" = "%1$@ set a new wallpaper for the chat with you"; +"PUSH_MESSAGE_SAME_WALLPAPER" = "%1$@ set the same wallpaper for the chat with you"; "PUSH_REMINDER_TITLE" = "🗓 Reminder"; "PUSH_SENDER_YOU" = "📅 You"; @@ -9121,12 +9121,13 @@ Sorry for the inconvenience."; "Premium.GiftedTitle.Someone" = "Someone"; -"Notification.ChangedWallpaper" = "%1$@ set a new background for this chat"; -"Notification.YouChangedWallpaper" = "You set a new background for this chat"; -"Notification.Wallpaper.View" = "View Background"; +"Notification.ChangedWallpaper" = "%1$@ set a new wallpaper for this chat"; +"Notification.YouChangedWallpaper" = "You set a new wallpaper for this chat"; +"Notification.YouChangingWallpaper" = "Setting new wallpaper"; +"Notification.Wallpaper.View" = "View Wallpaper"; -"Notification.ChangedToSameWallpaper" = "%1$@ set the same background for this chat"; -"Notification.YouChangedToSameWallpaper" = "You set the same background for this chat"; +"Notification.ChangedToSameWallpaper" = "%1$@ set the same wallpaper for this chat"; +"Notification.YouChangedToSameWallpaper" = "You set the same wallpaper for this chat"; "Channel.AdminLog.JoinedViaFolderInviteLink" = "%1$@ joined via invite link %2$@ (community)"; @@ -9144,25 +9145,27 @@ Sorry for the inconvenience."; "PeerInfo.Bot.ChangeSettings" = "Change Bot Settings"; "PeerInfo.Bot.BotFatherInfo" = "Use [@BotFather]() to manage this bot."; -"WallpaperPreview.NotAppliedInfo" = "Background will not be applied for **%@**"; -"WallpaperPreview.ChatTopText" = "Apply the background in this chat."; +"WallpaperPreview.NotAppliedInfo" = "**%@** will be able to apply this wallpaper"; +"WallpaperPreview.ChatTopText" = "Apply the wallpaper in this chat."; "WallpaperPreview.ChatBottomText" = "Enjoy the view."; -"Conversation.Theme.SetPhotoWallpaper" = "Choose Background from Photos"; -"Conversation.Theme.SetColorWallpaper" = "Set a Color as a Background"; -"Conversation.Theme.OtherOptions" = "Other Options..."; +"Conversation.Theme.SetPhotoWallpaper" = "Choose Wallpaper from Photos"; +"Conversation.Theme.SetColorWallpaper" = "Set a Color as Wallpaper"; -"Conversation.Theme.ChooseWallpaperTitle" = "Choose Background"; -"Conversation.Theme.ResetWallpaper" = "Reset to Default Background"; +"Conversation.Theme.ChooseWallpaperTitle" = "Choose Wallpaper"; +"Conversation.Theme.ResetWallpaper" = "Remove Wallpaper"; "Conversation.Theme.ChooseColorTitle" = "Set a Color"; "Conversation.Theme.SetCustomColor" = "Set Custom"; "Appearance.ShowNextMediaOnTap" = "Show Next Media on Tap"; +"Appearance.ShowNextMediaOnTapInfo" = "Tap near the edge of the screen while viewing media to navigate between photos."; "WebApp.LaunchMoreInfo" = "More about this bot"; "WebApp.LaunchConfirmation" = "To launch this web app, you will connect to its website."; -"WallpaperPreview.PreviewInNightMode" = "Preview this background in night mode."; -"WallpaperPreview.PreviewInDayMode" = "Preview this background in day mode."; +"WallpaperPreview.PreviewInNightMode" = "Preview this wallpaper in night mode."; +"WallpaperPreview.PreviewInDayMode" = "Preview this wallpaper in day mode."; + +"PeerInfo.BotBlockedTitle" = "Bot Blocked"; +"PeerInfo.BotBlockedText" = "This bot will not be able to message you."; -"Conversation.Theme.ApplyBackground" = "Set as Background"; diff --git a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift index 9cb348c33d1..a27fad04d59 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift @@ -759,7 +759,7 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { let date = context.component.date let components = date.0.components(separatedBy: " ") - let month = components.first ?? "" + let month = String(components.prefix(upTo: components.count - 1).joined(separator: " ")) let year = components.last ?? "" var monthAnimation: RollingText.AnimationDirection? From 62470a424f6be1d6d86fd838c8d7153c8ae9ed8f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 11 Apr 2023 22:25:41 +0400 Subject: [PATCH 40/57] Chat wallpaper improvements --- .../TGMediaPickerGalleryModel.h | 2 +- .../TGModernGalleryController.h | 2 + .../TGPhotoEditorController.h | 3 +- .../LegacyComponents/TGPhotoVideoEditor.h | 4 +- .../LegacyComponents/Sources/PGBlurTool.m | 5 + .../LegacyComponents/Sources/PGPhotoTool.h | 1 + .../LegacyComponents/Sources/PGPhotoTool.m | 5 + .../LegacyComponents/Sources/PGVignetteTool.m | 5 + .../Sources/TGMediaPickerGalleryModel.m | 9 +- .../Sources/TGModernGalleryController.m | 6 + .../Sources/TGPhotoEditorUtils.m | 2 +- .../Sources/TGPhotoToolsController.m | 5 + .../Sources/TGPhotoVideoEditor.m | 220 +++++++++++++++++- .../Sources/LegacyAttachmentMenu.swift | 47 +++- .../Sources/MediaPickerGridItem.swift | 9 +- .../Themes/CustomWallpaperPicker.swift | 18 +- .../ThemeColorsGridControllerNode.swift | 2 +- .../Sources/Themes/ThemeGridController.swift | 8 +- .../Themes/ThemeSettingsController.swift | 13 +- .../Themes/WallpaperGalleryController.swift | 50 +++- .../Sources/Themes/WallpaperGalleryItem.swift | 167 +++++++++++-- .../Themes/WallpaperOptionButtonNode.swift | 25 +- .../TelegramEngine/Themes/ChatThemes.swift | 25 +- submodules/TelegramPresentationData/BUILD | 1 + .../ChatControllerBackgroundNode.swift | 43 +++- .../Favorite.imageset/Contents.json | 12 + .../Media Grid/Favorite.imageset/heart_18.pdf | Bin 0 -> 1251 bytes .../Contents.json | 12 + .../WallpaperAdjustments.imageset/edit.pdf | Bin 0 -> 2089 bytes .../TelegramUI/Sources/ChatController.swift | 32 ++- ...ageProfilePhotoSuggestionContentNode.swift | 1 + ...hatMessageWallpaperBubbleContentNode.swift | 70 +++++- .../TelegramUI/Sources/ChatThemeScreen.swift | 129 +++++----- .../Sources/PeerInfo/PeerInfoScreen.swift | 11 +- .../Sources/WallpaperBackgroundNode.swift | 1 + .../Sources/WallpaperResources.swift | 2 +- .../WebUI/Sources/WebAppController.swift | 20 +- 37 files changed, 798 insertions(+), 169 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/heart_18.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/edit.pdf diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h index 8d2175553ff..4d4030a97d5 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h @@ -49,6 +49,6 @@ - (instancetype)initWithContext:(id)context items:(NSArray *)items focusItem:(id)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName; - (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab; -- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots; +- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots fromRect:(CGRect)fromRect; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryController.h index 533f54948ba..c65a9552f9d 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryController.h @@ -48,6 +48,8 @@ typedef NS_ENUM(NSUInteger, TGModernGalleryScrollAnimationDirection) { - (void)dismissWhenReady; - (void)dismissWhenReadyAnimated:(bool)animated; +- (void)setScrollViewHidden:(bool)hidden; + - (bool)isFullyOpaque; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h index e82f3b5c6dd..a1ac3a4f8b3 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h @@ -23,7 +23,8 @@ typedef enum { TGPhotoEditorControllerVideoIntent = (1 << 4), TGPhotoEditorControllerForumAvatarIntent = (1 << 5), TGPhotoEditorControllerSuggestedAvatarIntent = (1 << 6), - TGPhotoEditorControllerSuggestingAvatarIntent = (1 << 7) + TGPhotoEditorControllerSuggestingAvatarIntent = (1 << 7), + TGPhotoEditorControllerWallpaperIntent = (1 << 8) } TGPhotoEditorControllerIntent; @interface TGPhotoEditorController : TGOverlayController diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h index 737380548df..e58613cae3b 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h @@ -4,6 +4,8 @@ + (void)presentWithContext:(id)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video stickersContext:(id)stickersContext transitionView:(UIView *)transitionView senderName:(NSString *)senderName didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed; -+ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id)stickersContext snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed; ++ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString *)recipientName stickersContext:(id)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed; + ++ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed; @end diff --git a/submodules/LegacyComponents/Sources/PGBlurTool.m b/submodules/LegacyComponents/Sources/PGBlurTool.m index eedcb75f816..21cd6392b97 100644 --- a/submodules/LegacyComponents/Sources/PGBlurTool.m +++ b/submodules/LegacyComponents/Sources/PGBlurTool.m @@ -218,6 +218,11 @@ - (bool)isSimple return false; } +- (bool)isRegional +{ + return true; +} + - (bool)isAvialableForVideo { return false; diff --git a/submodules/LegacyComponents/Sources/PGPhotoTool.h b/submodules/LegacyComponents/Sources/PGPhotoTool.h index 7e2ec581562..474a17a5143 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoTool.h +++ b/submodules/LegacyComponents/Sources/PGPhotoTool.h @@ -35,6 +35,7 @@ typedef enum @property (nonatomic, readonly) NSInteger order; @property (nonatomic, readonly) bool isHidden; +@property (nonatomic, readonly) bool isRegional; @property (nonatomic, readonly) NSString *shaderString; @property (nonatomic, readonly) NSString *ancillaryShaderString; diff --git a/submodules/LegacyComponents/Sources/PGPhotoTool.m b/submodules/LegacyComponents/Sources/PGPhotoTool.m index 1c8a0263bef..d8dba44ec73 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoTool.m +++ b/submodules/LegacyComponents/Sources/PGPhotoTool.m @@ -38,6 +38,11 @@ - (bool)isSimple return true; } +- (bool)isRegional +{ + return false; +} + - (bool)isAvialableForVideo { return true; diff --git a/submodules/LegacyComponents/Sources/PGVignetteTool.m b/submodules/LegacyComponents/Sources/PGVignetteTool.m index 3e64b56e5b7..dc307df873e 100644 --- a/submodules/LegacyComponents/Sources/PGVignetteTool.m +++ b/submodules/LegacyComponents/Sources/PGVignetteTool.m @@ -38,6 +38,11 @@ - (bool)shouldBeSkipped return (ABS(((NSNumber *)self.displayValue).floatValue - (float)self.defaultValue) < FLT_EPSILON); } +- (bool)isRegional +{ + return true; +} + - (NSArray *)parameters { if (!_parameters) diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m index 53ebd775e0a..c786c200909 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m @@ -338,10 +338,10 @@ - (void)updateEditedItemView - (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab { - [self presentPhotoEditorForItem:item tab:tab snapshots:@[]]; + [self presentPhotoEditorForItem:item tab:tab snapshots:@[] fromRect:CGRectZero]; } -- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots +- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots fromRect:(CGRect)fromRect { __weak TGMediaPickerGalleryModel *weakSelf = self; @@ -356,12 +356,15 @@ - (void)presentPhotoEditorForItem:(id)item tab:(TGP CGRect refFrame = CGRectZero; UIView *editorReferenceView = [self referenceViewForItem:item frame:&refFrame]; + if (!CGRectEqualToRect(fromRect, CGRectZero)) { + refFrame = fromRect; + } UIView *referenceView = nil; UIImage *screenImage = nil; UIView *referenceParentView = nil; UIImage *image = nil; - UIView *entitiesView = nil; + UIView *entitiesView = nil; id editableMediaItem = item.editableMediaItem; diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryController.m b/submodules/LegacyComponents/Sources/TGModernGalleryController.m index 6596d5cf3f0..e38b44e82d2 100644 --- a/submodules/LegacyComponents/Sources/TGModernGalleryController.m +++ b/submodules/LegacyComponents/Sources/TGModernGalleryController.m @@ -166,6 +166,12 @@ - (void)dismissWhenReadyAnimated:(bool)animated { [self dismissWhenReadyAnimated:animated force:false]; } +- (void)setScrollViewHidden:(bool)hidden { + TGDispatchAfter(0.01, dispatch_get_main_queue(), ^{ + _view.scrollView.hidden = hidden; + }); +} + - (void)dismissWhenReadyAnimated:(bool)animated force:(bool)force { if (animated) { diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m b/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m index c2dfab89a49..6212c9d14e4 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m @@ -6,7 +6,7 @@ #import #import -const CGSize TGPhotoEditorResultImageMaxSize = { 1280, 1280 }; +const CGSize TGPhotoEditorResultImageMaxSize = { 2560, 2560 }; const CGSize TGPhotoEditorScreenImageHardLimitSize = { 1280, 1280 }; const CGSize TGPhotoEditorScreenImageHardLimitLegacySize = { 750, 750 }; diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolsController.m b/submodules/LegacyComponents/Sources/TGPhotoToolsController.m index f66777e0ae5..abede25b269 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoToolsController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoToolsController.m @@ -150,6 +150,9 @@ - (void)loadView } if (!tool.isHidden) { + if (tool.isRegional && self.intent == TGPhotoEditorControllerWallpaperIntent) { + continue; + } [tools addObject:tool]; if (tool.isSimple) [simpleTools addObject:tool]; @@ -1020,6 +1023,8 @@ - (TGPhotoEditorTab)availableTabs { if (self.photoEditor.forVideo) { return TGPhotoEditorToolsTab | TGPhotoEditorTintTab | TGPhotoEditorCurvesTab; + } else if (self.intent == TGPhotoEditorControllerWallpaperIntent) { + return TGPhotoEditorToolsTab | TGPhotoEditorTintTab | TGPhotoEditorCurvesTab; } else { return TGPhotoEditorToolsTab | TGPhotoEditorTintTab | TGPhotoEditorBlurTab | TGPhotoEditorCurvesTab; } diff --git a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m index 38e1c6ebdc5..60b958e2267 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m +++ b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m @@ -154,7 +154,7 @@ + (void)presentWithContext:(id)context parentController } } -+ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id)stickersContext snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed ++ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString *)recipientName stickersContext:(id)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed { id windowManager = [context makeOverlayWindowManager]; id windowContext = [windowManager context]; @@ -201,6 +201,11 @@ + (void)presentWithContext:(id)context controller:(TGVi [editingContext setCaption:caption forItem:editableItem]; }; + model.didFinishRenderingFullSizeImage = ^(id editableItem, UIImage *resultImage) + { + [editingContext setFullSizeImage:resultImage forItem:editableItem]; + }; + model.interfaceView.hasSwipeGesture = false; galleryController.model = model; @@ -255,10 +260,10 @@ + (void)presentWithContext:(id)context controller:(TGVi } }; - if (paint) { + if (paint || adjustments) { [model.interfaceView immediateEditorTransitionIn]; } - + for (UIView *view in snapshots) { [galleryController.view addSubview:view]; } @@ -269,9 +274,216 @@ + (void)presentWithContext:(id)context controller:(TGVi if (paint) { TGDispatchAfter(0.05, dispatch_get_main_queue(), ^{ - [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorPaintTab snapshots:snapshots]; + [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorPaintTab snapshots:snapshots fromRect:fromRect]; + }); + } else if (adjustments) { + TGDispatchAfter(0.05, dispatch_get_main_queue(), ^{ + [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorToolsTab snapshots:snapshots fromRect:fromRect]; }); } } ++ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed +{ + id windowManager = [context makeOverlayWindowManager]; + + TGMediaEditingContext *editingContext = [[TGMediaEditingContext alloc] init]; + + UIImage *thumbnailImage; + + TGPhotoEditorController *editorController = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:item intent:TGPhotoEditorControllerWallpaperIntent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:TGPhotoEditorToolsTab selectedTab:TGPhotoEditorToolsTab]; + editorController.editingContext = editingContext; + editorController.dontHideStatusBar = true; + + editorController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, __unused UIView **parentView) + { + *referenceFrame = fromRect; + + UIImageView *imageView = [[UIImageView alloc] initWithFrame:fromRect]; + //imageView.image = image; + + return imageView; + }; + + editorController.beginTransitionOut = ^UIView *(CGRect *referenceFrame, __unused UIView **parentView) + { + CGRect startFrame = CGRectZero; + if (referenceFrame != NULL) + { + startFrame = *referenceFrame; + *referenceFrame = fromRect; + } + + //[strongSelf transitionBackFromResultControllerWithReferenceFrame:startFrame]; + + return nil; //strongSelf->_previewView; + }; + + editorController.didFinishRenderingFullSizeImage = ^(UIImage *resultImage) + { + + }; + + __weak TGPhotoEditorController *weakController = editorController; + editorController.didFinishEditing = ^(id adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges, void(^commit)(void)) + { + if (!hasChanges) + return; + + __strong TGPhotoEditorController *strongController = weakController; + if (strongController == nil) + return; + + }; + editorController.requestThumbnailImage = ^(id editableItem) + { + return [editableItem thumbnailImageSignal]; + }; + + editorController.requestOriginalScreenSizeImage = ^(id editableItem, NSTimeInterval position) + { + return [editableItem screenImageSignal:position]; + }; + + editorController.requestOriginalFullSizeImage = ^(id editableItem, NSTimeInterval position) + { + if (editableItem.isVideo) { + if ([editableItem isKindOfClass:[TGMediaAsset class]]) { + return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem allowNetworkAccess:true]; + } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { + return ((TGCameraCapturedVideo *)editableItem).avAsset; + } else { + return [editableItem originalImageSignal:position]; + } + } else { + return [editableItem originalImageSignal:position]; + } + }; + + TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:editorController]; + controllerWindow.hidden = false; + controller.view.clipsToBounds = true; + +// TGModernGalleryController *galleryController = [[TGModernGalleryController alloc] initWithContext:windowContext]; +// galleryController.adjustsStatusBarVisibility = true; +// galleryController.animateTransition = false; +// galleryController.finishedTransitionIn = ^(id item, TGModernGalleryItemView *itemView) { +// appeared(); +// }; +// //galleryController.hasFadeOutTransition = true; +// +// id galleryItem = nil; +// if (item.isVideo) +// galleryItem = [[TGMediaPickerGalleryVideoItem alloc] initWithAsset:item]; +// else +// galleryItem = [[TGMediaPickerGalleryPhotoItem alloc] initWithAsset:item]; +// galleryItem.editingContext = editingContext; +// galleryItem.stickersContext = stickersContext; +// +// TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:@[galleryItem] focusItem:galleryItem selectionContext:nil editingContext:editingContext hasCaptions:true allowCaptionEntities:true hasTimer:false onlyCrop:false inhibitDocumentCaptions:false hasSelectionPanel:false hasCamera:false recipientName:recipientName]; +// model.controller = galleryController; +// model.stickersContext = stickersContext; +// +// model.willFinishEditingItem = ^(id editableItem, id adjustments, id representation, bool hasChanges) +// { +// if (hasChanges) +// { +// [editingContext setAdjustments:adjustments forItem:editableItem]; +// [editingContext setTemporaryRep:representation forItem:editableItem]; +// } +// }; +// +// model.didFinishEditingItem = ^(id editableItem, __unused id adjustments, UIImage *resultImage, UIImage *thumbnailImage) +// { +// [editingContext setImage:resultImage thumbnailImage:thumbnailImage forItem:editableItem synchronous:false]; +// }; +// +// model.saveItemCaption = ^(id editableItem, NSAttributedString *caption) +// { +// [editingContext setCaption:caption forItem:editableItem]; +// }; +// +// model.didFinishRenderingFullSizeImage = ^(id editableItem, UIImage *resultImage) +// { +// [editingContext setFullSizeImage:resultImage forItem:editableItem]; +// }; +// +// model.interfaceView.hasSwipeGesture = false; +// galleryController.model = model; +// +// __weak TGModernGalleryController *weakGalleryController = galleryController; +// +// [model.interfaceView updateSelectionInterface:1 counterVisible:false animated:false]; +// model.interfaceView.thumbnailSignalForItem = ^SSignal *(id item) +// { +// return nil; +// }; +// model.interfaceView.donePressed = ^(TGMediaPickerGalleryItem *item) +// { +// __strong TGModernGalleryController *strongController = weakGalleryController; +// if (strongController == nil) +// return; +// +// if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]]) +// { +// TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item]; +// [itemView stop]; +// [itemView setPlayButtonHidden:true animated:true]; +// } +// +// if (completion != nil) +// completion(item.asset, editingContext); +// +// [strongController dismissWhenReadyAnimated:true]; +// }; +// +// galleryController.beginTransitionIn = ^UIView *(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView) +// { +// return nil; +// }; +// +// galleryController.beginTransitionOut = ^UIView *(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView) +// { +// return nil; +// }; +// +// galleryController.completedTransitionOut = ^ +// { +// TGModernGalleryController *strongGalleryController = weakGalleryController; +// if (strongGalleryController != nil && strongGalleryController.overlayWindow == nil) +// { +// TGNavigationController *navigationController = (TGNavigationController *)strongGalleryController.navigationController; +// TGOverlayControllerWindow *window = (TGOverlayControllerWindow *)navigationController.view.window; +// if ([window isKindOfClass:[TGOverlayControllerWindow class]]) +// [window dismiss]; +// } +// if (dismissed) { +// dismissed(); +// } +// }; +// +// if (paint || adjustments) { +// [model.interfaceView immediateEditorTransitionIn]; +// } +// +// for (UIView *view in snapshots) { +// [galleryController.view addSubview:view]; +// } +// +// TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:galleryController]; +// controllerWindow.hidden = false; +// galleryController.view.clipsToBounds = true; +// +// if (paint) { +// TGDispatchAfter(0.05, dispatch_get_main_queue(), ^{ +// [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorPaintTab snapshots:snapshots fromRect:fromRect]; +// }); +// } else if (adjustments) { +// TGDispatchAfter(0.05, dispatch_get_main_queue(), ^{ +// [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorToolsTab snapshots:snapshots fromRect:fromRect]; +// }); +// } +} + + @end diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index 8ea2a99219e..2adce3e163d 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -58,7 +58,48 @@ public enum LegacyAttachmentMenuMediaEditing { case file } -public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: String?, media: AnyMediaReference, initialCaption: NSAttributedString, snapshots: [UIView], transitionCompletion: (() -> Void)?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, present: @escaping (ViewController, Any?) -> Void) { +public enum LegacyMediaEditorMode { + case draw + case adjustments +} + +public func legacyWallpaperEditor(context: AccountContext, image: UIImage, fromRect: CGRect, mainSnapshot: UIView, snapshots: [UIView], transitionCompletion: (() -> Void)?, completion: @escaping (UIImage, TGMediaEditAdjustments?) -> Void, present: @escaping (ViewController, Any?) -> Void) { + let item = TGCameraCapturedPhoto(existing: image) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil) + legacyController.blocksBackgroundWhenInOverlay = true + legacyController.acceptsFocusWhenInOverlay = true + legacyController.statusBar.statusBarStyle = .Ignore + legacyController.controllerLoaded = { [weak legacyController] in + legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true + } + + let emptyController = LegacyEmptyController(context: legacyController.context)! + emptyController.navigationBarShouldBeHidden = true + let navigationController = makeLegacyNavigationController(rootController: emptyController) + navigationController.setNavigationBarHidden(true, animated: false) + legacyController.bind(controller: navigationController) + + legacyController.enableSizeClassSignal = true + + present(legacyController, nil) + + TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, withItem: item, from: fromRect, mainSnapshot: mainSnapshot, snapshots: snapshots as [Any], completion: { item, editingContext in + let adjustments = editingContext?.adjustments(for: item) + if let imageSignal = editingContext?.fullSizeImageUrl(for: item) { + imageSignal.start(next: { value in + if let value = value as? NSURL, let data = try? Data(contentsOf: value as URL), let image = UIImage(data: data) { + completion(image, adjustments) + } + }, error: { _ in }, completed: {}) + } + }, dismissed: { [weak legacyController] in + legacyController?.dismiss() + }) +} + +public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: String?, media: AnyMediaReference, mode: LegacyMediaEditorMode, initialCaption: NSAttributedString, snapshots: [UIView], transitionCompletion: (() -> Void)?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, present: @escaping (ViewController, Any?) -> Void) { let _ = (fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: media) |> deliverOnMainQueue).start(next: { (value, isImage) in guard case let .data(data) = value, data.complete else { @@ -107,7 +148,7 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: present(legacyController, nil) - TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: true, recipientName: recipientName, stickersContext: paintStickersContext, snapshots: snapshots as [Any], immediate: transitionCompletion != nil, appeared: { + TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: mode == .draw, adjustments: mode == .adjustments, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: snapshots as [Any], immediate: transitionCompletion != nil, appeared: { transitionCompletion?() }, completion: { result, editingContext in let nativeGenerator = legacyAssetPickerItemGenerator() @@ -365,7 +406,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl present(legacyController, nil) - TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: NSAttributedString(), withItem: item, paint: false, recipientName: recipientName, stickersContext: paintStickersContext, snapshots: [], immediate: false, appeared: { + TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: NSAttributedString(), withItem: item, paint: false, adjustments: false, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: [], immediate: false, appeared: { }, completion: { result, editingContext in let nativeGenerator = legacyAssetPickerItemGenerator() var selectableResult: TGMediaSelectableItem? diff --git a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift index dac85ccde46..6d65780560a 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift @@ -323,7 +323,14 @@ final class MediaPickerGridItemNode: GridItemNode { strongSelf.updateHasSpoiler(hasSpoiler) })) - if asset.mediaType == .video { + if asset.isFavorite { + self.typeIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Favorite"), color: .white) + if self.typeIconNode.supernode == nil { + self.addSubnode(self.gradientNode) + self.addSubnode(self.typeIconNode) + self.setNeedsLayout() + } + } else if asset.mediaType == .video { if asset.mediaSubtypes.contains(.videoHighFrameRate) { self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaSlomo") } else if asset.mediaSubtypes.contains(.videoTimelapse) { diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index 59fd6e67c84..c5d5b92ee7d 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -25,9 +25,9 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V controller.selectionBlock = { [weak legacyController] asset, _ in if let asset = asset { let controller = WallpaperGalleryController(context: context, source: .asset(asset.backingAsset)) - controller.apply = { [weak legacyController, weak controller] wallpaper, mode, cropRect, brightness in + controller.apply = { [weak legacyController, weak controller] wallpaper, mode, editedImage, cropRect, brightness in if let legacyController = legacyController, let controller = controller { - uploadCustomWallpaper(context: context, wallpaper: wallpaper, mode: mode, cropRect: cropRect, brightness: brightness, completion: { [weak legacyController, weak controller] in + uploadCustomWallpaper(context: context, wallpaper: wallpaper, mode: mode, editedImage: nil, cropRect: cropRect, brightness: brightness, completion: { [weak legacyController, weak controller] in if let legacyController = legacyController, let controller = controller { legacyController.dismiss() controller.dismiss(forceAway: true) @@ -47,8 +47,8 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V }) } -func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightness: CGFloat?, completion: @escaping () -> Void) { - let imageSignal: Signal +func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, completion: @escaping () -> Void) { + var imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): switch wallpaper { @@ -112,6 +112,10 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE } } + if let editedImage { + imageSignal = .single(editedImage) + } + let _ = (imageSignal |> map { image -> UIImage in var croppedImage = UIImage() @@ -196,7 +200,7 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE }).start() } -public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) { +public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) { var imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): @@ -263,6 +267,10 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa } } + if let editedImage { + imageSignal = .single(editedImage) + } + let _ = (imageSignal |> map { image -> UIImage in var croppedImage = UIImage() diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift index 387b7733749..64e2c7f57b1 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift @@ -158,7 +158,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { } controller.navigationPresentation = .modal - controller.apply = { [weak self] wallpaper, _, _, _ in + controller.apply = { [weak self] wallpaper, _, _, _, _ in if let strongSelf = self, let mode = strongSelf.controller?.mode, case let .peer(peer) = mode, case let .wallpaper(wallpaperValue, _) = wallpaper { let _ = (strongSelf.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: wallpaperValue) |> deliverOnMainQueue).start(completed: { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index cef1f6070ca..048a0af79e2 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -120,9 +120,9 @@ public final class ThemeGridController: ViewController { self.displayNode = ThemeGridControllerNode(context: self.context, presentationData: self.presentationData, presentPreviewController: { [weak self] source in if let strongSelf = self { let controller = WallpaperGalleryController(context: strongSelf.context, source: source) - controller.apply = { [weak self, weak controller] wallpaper, options, cropRect, brightness in + controller.apply = { [weak self, weak controller] wallpaper, options, editedImage, cropRect, brightness in if let strongSelf = self { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in if let strongSelf = self { strongSelf.deactivateSearch(animated: false) strongSelf.controllerNode.scrollToTop(animated: false) @@ -148,9 +148,9 @@ public final class ThemeGridController: ViewController { return } let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset)) - controller.apply = { [weak self, weak controller] wallpaper, options, cropRect, brightness in + controller.apply = { [weak self, weak controller] wallpaper, options, editedImage, cropRect, brightness in if let strongSelf = self, let controller = controller { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, completion: { [weak controller] in + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak controller] in if let controller = controller { controller.dismiss(forceAway: true) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 0e3fd9f8945..7fa3424a027 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -129,7 +129,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { case stickersAndEmoji case otherHeader(PresentationTheme, String) case showNextMediaOnTap(PresentationTheme, String, Bool) - case animationsInfo(PresentationTheme, String) + case showNextMediaOnTapInfo(PresentationTheme, String) var section: ItemListSectionId { switch self { @@ -143,7 +143,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return ThemeSettingsControllerSection.icon.rawValue case .powerSaving, .stickersAndEmoji: return ThemeSettingsControllerSection.message.rawValue - case .otherHeader, .showNextMediaOnTap, .animationsInfo: + case .otherHeader, .showNextMediaOnTap, .showNextMediaOnTapInfo: return ThemeSettingsControllerSection.other.rawValue } } @@ -180,7 +180,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return 13 case .showNextMediaOnTap: return 14 - case .animationsInfo: + case .showNextMediaOnTapInfo: return 15 } } @@ -277,8 +277,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } else { return false } - case let .animationsInfo(lhsTheme, lhsText): - if case let .animationsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .showNextMediaOnTapInfo(lhsTheme, lhsText): + if case let .showNextMediaOnTapInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false @@ -347,7 +347,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleShowNextMediaOnTap(value) }, tag: ThemeSettingsEntryTag.animations) - case let .animationsInfo(_, text): + case let .showNextMediaOnTapInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) } } @@ -405,6 +405,7 @@ private func themeSettingsControllerEntries(presentationData: PresentationData, entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased())) entries.append(.showNextMediaOnTap(presentationData.theme, strings.Appearance_ShowNextMediaOnTap, mediaSettings.showNextMediaOnTap)) + entries.append(.showNextMediaOnTapInfo(presentationData.theme, strings.Appearance_ShowNextMediaOnTapInfo)) return entries } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift index 3dc081ba427..cfd9702754e 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift @@ -16,6 +16,8 @@ import GalleryUI import HexColor import CounterContollerTitleView import UndoUI +import LegacyComponents +import LegacyMediaPickerUI public enum WallpaperListType { case wallpapers(WallpaperPresentationOptions?) @@ -164,6 +166,14 @@ private func updatedFileWallpaper(id: Int64? = nil, accessHash: Int64? = nil, sl return .file(TelegramWallpaper.File(id: id ?? 0, accessHash: accessHash ?? 0, isCreator: false, isDefault: false, isPattern: isPattern, isDark: false, slug: slug, file: file, settings: WallpaperSettings(colors: colorValues, intensity: intensityValue, rotation: rotation))) } +class WallpaperGalleryInteraction { + let editMedia: (UIImage, CGRect, UIView, [UIView], @escaping (UIImage, TGMediaEditAdjustments?) -> Void) -> Void + + init(editMedia: @escaping (UIImage, CGRect, UIView, [UIView], @escaping (UIImage, TGMediaEditAdjustments?) -> Void) -> Void) { + self.editMedia = editMedia + } +} + public class WallpaperGalleryController: ViewController { public enum Mode { case `default` @@ -176,7 +186,9 @@ public class WallpaperGalleryController: ViewController { private let context: AccountContext private let source: WallpaperListSource private let mode: Mode - public var apply: ((WallpaperGalleryEntry, WallpaperPresentationOptions, CGRect?, CGFloat?) -> Void)? + public var apply: ((WallpaperGalleryEntry, WallpaperPresentationOptions, UIImage?, CGRect?, CGFloat?) -> Void)? + + private var interaction: WallpaperGalleryInteraction? private let _ready = Promise() override public var ready: Promise { @@ -230,9 +242,29 @@ public class WallpaperGalleryController: ViewController { //self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.interaction = WallpaperGalleryInteraction(editMedia: { [weak self] image, fromRect, mainSnapshot, snapshots, apply in + guard let self else { + return + } + var snapshots = snapshots + if let toolbarNode = self.toolbarNode, let snapshotView = toolbarNode.view.snapshotContentTree() { + snapshotView.frame = toolbarNode.view.convert(toolbarNode.view.bounds, to: nil) + snapshots.append(snapshotView) + } + + legacyWallpaperEditor(context: context, image: image, fromRect: fromRect, mainSnapshot: mainSnapshot, snapshots: snapshots, transitionCompletion: { + + }, completion: { image, adjustments in + apply(image, adjustments) + }, present: { [weak self] c, a in + if let self { + self.present(c, in: .window(.root)) + } + }) + }) + var entries: [WallpaperGalleryEntry] = [] var centralEntryIndex: Int? - switch source { case let .list(wallpapers, central, type): entries = wallpapers.map { .wallpaper($0, nil) } @@ -350,7 +382,7 @@ public class WallpaperGalleryController: ViewController { var i: Int = 0 var updateItems: [GalleryPagerUpdateItem] = [] for entry in entries { - let item = GalleryPagerUpdateItem(index: i, previousIndex: i, item: WallpaperGalleryItem(context: self.context, index: updateItems.count, entry: entry, arguments: arguments, source: self.source, mode: self.mode)) + let item = GalleryPagerUpdateItem(index: i, previousIndex: i, item: WallpaperGalleryItem(context: self.context, index: updateItems.count, entry: entry, arguments: arguments, source: self.source, mode: self.mode, interaction: self.interaction!)) updateItems.append(item) i += 1 } @@ -361,7 +393,7 @@ public class WallpaperGalleryController: ViewController { var updateItems: [GalleryPagerUpdateItem] = [] for i in 0 ..< self.entries.count { if i == index { - let item = GalleryPagerUpdateItem(index: index, previousIndex: index, item: WallpaperGalleryItem(context: self.context, index: index, entry: entry, arguments: arguments, source: self.source, mode: self.mode)) + let item = GalleryPagerUpdateItem(index: index, previousIndex: index, item: WallpaperGalleryItem(context: self.context, index: index, entry: entry, arguments: arguments, source: self.source, mode: self.mode, interaction: self.interaction!)) updateItems.append(item) } } @@ -459,7 +491,7 @@ public class WallpaperGalleryController: ViewController { let entry = strongSelf.entries[centralItemNode.index] if case .peer = strongSelf.mode { - strongSelf.apply?(entry, options, centralItemNode.cropRect, centralItemNode.brightness) + strongSelf.apply?(entry, options, centralItemNode.editedImage, centralItemNode.cropRect, centralItemNode.brightness) return } @@ -617,7 +649,7 @@ public class WallpaperGalleryController: ViewController { break } - strongSelf.apply?(entry, options, centralItemNode.cropRect, centralItemNode.brightness) + strongSelf.apply?(entry, options, nil, centralItemNode.cropRect, centralItemNode.brightness) } } } @@ -695,7 +727,7 @@ public class WallpaperGalleryController: ViewController { break } } - strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring)) + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) } } @@ -723,7 +755,7 @@ public class WallpaperGalleryController: ViewController { itemNode.updateIsColorsPanelActive(strongSelf.colorsPanelEnabled, animated: true) - strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring)) + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) } } @@ -960,7 +992,7 @@ public class WallpaperGalleryController: ViewController { colors = true } - self.galleryNode.pager.replaceItems(zip(0 ..< self.entries.count, self.entries).map({ WallpaperGalleryItem(context: self.context, index: $0, entry: $1, arguments: WallpaperGalleryItemArguments(isColorsList: colors), source: self.source, mode: self.mode) }), centralItemIndex: self.centralEntryIndex) + self.galleryNode.pager.replaceItems(zip(0 ..< self.entries.count, self.entries).map({ WallpaperGalleryItem(context: self.context, index: $0, entry: $1, arguments: WallpaperGalleryItemArguments(isColorsList: colors), source: self.source, mode: self.mode, interaction: self.interaction!) }), centralItemIndex: self.centralEntryIndex) if let initialOptions = self.initialOptions, let itemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { itemNode.options = initialOptions diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 410e741e344..6de92eefa21 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -46,25 +46,27 @@ class WallpaperGalleryItem: GalleryItem { let arguments: WallpaperGalleryItemArguments let source: WallpaperListSource let mode: WallpaperGalleryController.Mode + let interaction: WallpaperGalleryInteraction - init(context: AccountContext, index: Int, entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode) { + init(context: AccountContext, index: Int, entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode, interaction: WallpaperGalleryInteraction) { self.context = context self.index = index self.entry = entry self.arguments = arguments self.source = source self.mode = mode + self.interaction = interaction } func node(synchronous: Bool) -> GalleryItemNode { let node = WallpaperGalleryItemNode(context: self.context) - node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode) + node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode, interaction: self.interaction) return node } func updateNode(node: GalleryItemNode, synchronous: Bool) { if let node = node as? WallpaperGalleryItemNode { - node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode) + node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode, interaction: self.interaction) } } @@ -93,6 +95,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private var colorPreview: Bool = false private var contentSize: CGSize? private var arguments = WallpaperGalleryItemArguments() + private var interaction: WallpaperGalleryInteraction? let wrapperNode: ASDisplayNode let imageNode: TransformImageNode @@ -105,7 +108,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private let cancelButtonNode: WallpaperNavigationButtonNode private let shareButtonNode: WallpaperNavigationButtonNode private let dayNightButtonNode: WallpaperNavigationButtonNode - + private let editButtonNode: WallpaperNavigationButtonNode + private let blurButtonNode: WallpaperOptionButtonNode private let motionButtonNode: WallpaperOptionButtonNode private let patternButtonNode: WallpaperOptionButtonNode @@ -176,8 +180,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.45)) - self.serviceBackgroundNode.isHidden = true + self.serviceBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.35)) var sliderValueChangedImpl: ((CGFloat) -> Void)? self.sliderNode = WallpaperSliderNode(minValue: 0.0, maxValue: 1.0, value: 0.7, valueChanged: { value, _ in @@ -192,6 +195,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.shareButtonNode.enableSaturation = true self.dayNightButtonNode = WallpaperNavigationButtonNode(content: .dayNight(isNight: self.isDarkAppearance), dark: true) self.dayNightButtonNode.enableSaturation = true + self.editButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Settings/WallpaperAdjustments"), size: CGSize(width: 28.0, height: 28.0)), dark: true) + self.editButtonNode.enableSaturation = true self.playButtonPlayImage = generateImage(CGSize(width: 48.0, height: 48.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -256,6 +261,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.addSubnode(self.cancelButtonNode) self.addSubnode(self.shareButtonNode) self.addSubnode(self.dayNightButtonNode) + self.addSubnode(self.editButtonNode) self.imageNode.addSubnode(self.brightnessNode) @@ -267,6 +273,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) self.shareButtonNode.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside) self.dayNightButtonNode.addTarget(self, action: #selector(self.dayNightPressed), forControlEvents: .touchUpInside) + self.editButtonNode.addTarget(self, action: #selector(self.editPressed), forControlEvents: .touchUpInside) sliderValueChangedImpl = { [weak self] value in if let self { @@ -288,7 +295,13 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } switch entry { case .asset, .contextResult: - return self.cropNode.cropRect + if let editedImage = self.editedImage { + let scale = editedImage.size.height / self.cropNode.cropRect.height + let cropRect = self.cropNode.cropRect + return CGRect(origin: CGPoint(x: cropRect.minX * scale, y: cropRect.minY * scale), size: CGSize(width: cropRect.width * scale, height: cropRect.height * scale)) + } else { + return self.cropNode.cropRect + } default: return nil } @@ -453,6 +466,70 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.switchTheme() } + @objc private func editPressed() { + guard let image = self.imageNode.image else { + return + } + + let originalImage = self.originalImage ?? image + + var nodesToSnapshot = [ + self.cancelButtonNode, + self.editButtonNode, + self.blurButtonNode, + self.motionButtonNode, + self.serviceBackgroundNode + ] + + if let messageNodes = self.messageNodes { + for node in messageNodes { + nodesToSnapshot.append(node) + } + } + + var snapshots: [UIView] = [] + for node in nodesToSnapshot { + if let snapshotView = node.view.snapshotContentTree() { + snapshotView.frame = node.view.convert(node.view.bounds, to: nil) + snapshots.append(snapshotView) + } + } + + if let snapshotView = self.dayNightButtonNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.dayNightButtonNode.view.convert(self.dayNightButtonNode.view.bounds, to: nil) + snapshots.append(snapshotView) + } + + let mainSnapshotView: UIView + if let snapshotView = self.imageNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.imageNode.view.convert(self.imageNode.view.bounds, to: nil) + mainSnapshotView = snapshotView + } else { + mainSnapshotView = UIView() + } + + let fromRect = self.imageNode.view.convert(self.imageNode.bounds, to: nil) + self.interaction?.editMedia(originalImage, fromRect, mainSnapshotView, snapshots, { [weak self] result, adjustments in + self?.originalImage = originalImage + self?.editedImage = result + self?.currentAdjustments = adjustments + + self?.imageNode.setSignal(.single({ arguments in + let context = DrawingContext(size: arguments.drawingSize, opaque: false) + context?.withFlippedContext({ context in + if let cgImage = result.cgImage { + context.draw(cgImage, in: CGRect(origin: .zero, size: arguments.drawingSize)) + } + }) + return context + })) + }) + } + + private var originalImage: UIImage? + public private(set) var editedImage: UIImage? + private var currentAdjustments: TGMediaEditAdjustments? + private func animateIntensityChange(delay: Double) { let targetValue: CGFloat = self.sliderNode.value self.sliderNode.internalUpdateLayout(size: self.sliderNode.frame.size, value: 1.0) @@ -484,11 +561,12 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.dismiss() } - func setEntry(_ entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode) { + func setEntry(_ entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode, interaction: WallpaperGalleryInteraction) { let previousArguments = self.arguments self.arguments = arguments self.source = source self.mode = mode + self.interaction = interaction if self.arguments.colorPreview != previousArguments.colorPreview { if self.arguments.colorPreview { @@ -530,6 +608,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) + var isColor = false switch entry { case let .wallpaper(wallpaper, _): Queue.mainQueue().justDispatch { @@ -545,6 +624,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } else { self.playButtonNode.setIcon(self.playButtonRotateImage) } + isColor = true } else if case let .gradient(gradient) = wallpaper { self.nativeNode.isHidden = false self.nativeNode.update(wallpaper: wallpaper) @@ -555,10 +635,12 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } else { self.playButtonNode.setIcon(self.playButtonRotateImage) } + isColor = true } else if case .color = wallpaper { self.nativeNode.isHidden = false self.nativeNode.update(wallpaper: wallpaper) self.patternButtonNode.isSelected = false + isColor = true } else { self.nativeNode._internalUpdateIsSettingUpWallpaper() self.nativeNode.isHidden = true @@ -575,8 +657,21 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.patternButtonNode.isSelected = false self.playButtonNode.setIcon(self.playButtonRotateImage) } + + self.cancelButtonNode.enableSaturation = isColor + self.dayNightButtonNode.enableSaturation = isColor + self.editButtonNode.enableSaturation = isColor + self.shareButtonNode.enableSaturation = isColor + self.patternButtonNode.backgroundNode.enableSaturation = isColor + self.blurButtonNode.backgroundNode.enableSaturation = isColor + self.motionButtonNode.backgroundNode.enableSaturation = isColor + self.colorsButtonNode.backgroundNode.enableSaturation = isColor + self.playButtonNode.enableSaturation = isColor var canShare = false + var canSwitchTheme = false + var canEdit = false + switch entry { case let .wallpaper(wallpaper, message): self.initialWallpaper = wallpaper @@ -749,7 +844,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3)) self.wrapperNode.addSubnode(self.cropNode) showPreviewTooltip = true - self.serviceBackgroundNode.isHidden = false + canSwitchTheme = true + canEdit = true case let .contextResult(result): var imageDimensions: CGSize? var imageResource: TelegramMediaResource? @@ -805,11 +901,24 @@ final class WallpaperGalleryItemNode: GalleryItemNode { subtitleSignal = .single(nil) self.wrapperNode.addSubnode(self.cropNode) showPreviewTooltip = true - self.serviceBackgroundNode.isHidden = false + canSwitchTheme = true } self.contentSize = contentSize - self.shareButtonNode.isHidden = !canShare + if case .wallpaper = source { + canSwitchTheme = true + } else if case let .list(_, _, type) = source, case .colors = type { + canSwitchTheme = true + } + + if canSwitchTheme { + self.dayNightButtonNode.isHidden = false + self.shareButtonNode.isHidden = true + } else { + self.dayNightButtonNode.isHidden = true + self.shareButtonNode.isHidden = !canShare + } + self.editButtonNode.isHidden = !canEdit if self.cropNode.supernode == nil { self.imageNode.contentMode = .scaleAspectFill @@ -1157,16 +1266,22 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let alpha = 1.0 - min(1.0, max(0.0, abs(offset.y) / 50.0)) var additionalYOffset: CGFloat = 0.0 + var canEditIntensity = false if let source = self.source { switch source { case .asset, .contextResult: - if self.isDarkAppearance { - additionalYOffset -= 44.0 + canEditIntensity = true + case let .wallpaper(wallpaper, _, _, _, _, _): + if case let .file(file) = wallpaper, !file.isPattern { + canEditIntensity = true } default: break } } + if canEditIntensity && self.isDarkAppearance { + additionalYOffset -= 44.0 + } let buttonSpacing: CGFloat = 18.0 @@ -1201,13 +1316,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } else { sliderFrame = sliderFrame.offsetBy(dx: 0.0, dy: 22.0) } - - var dayNightHidden = true - + let cancelSize = self.cancelButtonNode.measure(layout.size) let cancelFrame = CGRect(origin: CGPoint(x: 16.0 + offset.x, y: 16.0), size: cancelSize) - let shareFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - 28.0 + offset.x, y: 16.0), size: CGSize(width: 28.0, height: 28.0)) + let editFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - 28.0 + offset.x - 46.0, y: 16.0), size: CGSize(width: 28.0, height: 28.0)) let centerOffset: CGFloat = 32.0 @@ -1218,13 +1331,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode { blurFrame = leftButtonFrame motionAlpha = 1.0 motionFrame = rightButtonFrame - dayNightHidden = false case .contextResult: blurAlpha = 1.0 blurFrame = leftButtonFrame motionAlpha = 1.0 motionFrame = rightButtonFrame - dayNightHidden = false case let .wallpaper(wallpaper, _): switch wallpaper { case .builtin: @@ -1317,8 +1428,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame) transition.updateFrame(node: self.shareButtonNode, frame: shareFrame) transition.updateFrame(node: self.dayNightButtonNode, frame: shareFrame) - - self.dayNightButtonNode.isHidden = dayNightHidden + transition.updateFrame(node: self.editButtonNode, frame: editFrame) } private func updateMessagesLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) { @@ -1342,6 +1452,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { currentWallpaper = wallpaper } + var canEditIntensity = false if let source = self.source { switch source { case .slug, .wallpaper: @@ -1364,6 +1475,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode { if hasAnimatableGradient { bottomMessageText = presentationData.strings.WallpaperPreview_PreviewBottomTextAnimatable } + + if case let .wallpaper(wallpaper, _, _, _, _, _) = source, case let .file(file) = wallpaper, !file.isPattern { + canEditIntensity = true + } case let .list(_, _, type): switch type { case .wallpapers: @@ -1393,9 +1508,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { case .asset, .contextResult: topMessageText = presentationData.strings.WallpaperPreview_CropTopText bottomMessageText = presentationData.strings.WallpaperPreview_CropBottomText - if self.isDarkAppearance { - bottomInset += 44.0 - } + canEditIntensity = true case .customColor: topMessageText = presentationData.strings.WallpaperPreview_CustomColorTopText bottomMessageText = presentationData.strings.WallpaperPreview_CustomColorBottomText @@ -1409,6 +1522,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode { serviceMessageText = presentationData.strings.WallpaperPreview_NotAppliedInfo(peer.compactDisplayTitle).string } } + + if canEditIntensity && self.isDarkAppearance { + bottomInset += 44.0 + } let theme = self.presentationData.theme @@ -1567,7 +1684,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.45)), 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.35)), 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) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 25649e2875a..beebc7850e3 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -41,7 +41,7 @@ final class WallpaperLightButtonBackgroundNode: ASDisplayNode { private let lightNode: ASDisplayNode override init() { - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.45), enableBlur: true, enableSaturation: false) + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: false) self.overlayNode = ASDisplayNode() self.overlayNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.75) self.overlayNode.layer.compositingFilter = "overlayBlendMode" @@ -71,8 +71,15 @@ final class WallpaperLightButtonBackgroundNode: ASDisplayNode { final class WallpaperOptionBackgroundNode: ASDisplayNode { private let backgroundNode: NavigationBackgroundNode + var enableSaturation: Bool { + didSet { + self.backgroundNode.updateColor(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: self.enableSaturation, transition: .immediate) + } + } + init(enableSaturation: Bool = false) { - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.45), enableBlur: true, enableSaturation: enableSaturation) + self.enableSaturation = enableSaturation + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: enableSaturation) super.init() @@ -97,7 +104,13 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { case dayNight(isNight: Bool) } - var enableSaturation: Bool = false + var enableSaturation: Bool = false { + didSet { + if let backgroundNode = self.backgroundNode as? WallpaperOptionBackgroundNode { + backgroundNode.enableSaturation = self.enableSaturation + } + } + } private let content: Content var dark: Bool { @@ -128,7 +141,7 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { self.dark = dark if dark { - self.backgroundNode = WallpaperOptionBackgroundNode() + self.backgroundNode = WallpaperOptionBackgroundNode(enableSaturation: self.enableSaturation) } else { self.backgroundNode = WallpaperLightButtonBackgroundNode() } @@ -252,7 +265,7 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { - private let backgroundNode: WallpaperOptionBackgroundNode + let backgroundNode: WallpaperOptionBackgroundNode private let checkNode: CheckNode private let colorNode: ASImageNode @@ -488,7 +501,7 @@ final class WallpaperSliderNode: ASDisplayNode { self.value = value self.valueChanged = valueChanged - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.45), enableBlur: true, enableSaturation: false) + self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: false) self.foregroundNode = ASDisplayNode() self.foregroundNode.clipsToBounds = true diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index dc247c1097a..406394d9379 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -124,18 +124,8 @@ func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager guard let inputPeer = apiInputPeer(peer) else { return .complete() } - return postbox.transaction { transaction -> Signal in - transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in - if let current = current as? CachedUserData { - return current.withUpdatedWallpaper(wallpaper) - } else { - return current - } - }) - var flags: Int32 = 0 - var inputWallpaper: Api.InputWallPaper? var inputSettings: Api.WallPaperSettings? if let inputWallpaperAndInputSettings = wallpaper?.apiInputWallpaperAndSettings { @@ -149,10 +139,19 @@ func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager return .complete() } |> mapToSignal { updates -> Signal in - if applyUpdates { - stateManager.addUpdates(updates) + return postbox.transaction { transaction -> Api.Updates in + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + if let current = current as? CachedUserData { + return current.withUpdatedWallpaper(wallpaper) + } else { + return current + } + }) + if applyUpdates { + stateManager.addUpdates(updates) + } + return updates } - return .single(updates) } } |> switchToLatest } diff --git a/submodules/TelegramPresentationData/BUILD b/submodules/TelegramPresentationData/BUILD index d15b4502631..75689afad79 100644 --- a/submodules/TelegramPresentationData/BUILD +++ b/submodules/TelegramPresentationData/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/StringPluralization:StringPluralization", "//submodules/Sunrise:Sunrise", "//submodules/TinyThumbnail:TinyThumbnail", + "//submodules/FastBlur:FastBlur", "//Telegram:PresentationStrings", ], visibility = [ diff --git a/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift b/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift index a410693ee45..884cae8f3a1 100644 --- a/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift +++ b/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift @@ -8,6 +8,7 @@ import Postbox import MediaResources import AppBundle import TinyThumbnail +import FastBlur private var backgroundImageForWallpaper: (TelegramWallpaper, Bool, UIImage)? @@ -200,8 +201,25 @@ public func chatControllerBackgroundImageSignal(wallpaper: TelegramWallpaper, me } else if !didOutputBlurred { didOutputBlurred = true if let immediateThumbnailData = file.file.immediateThumbnailData, let decodedData = decodeTinyThumbnail(data: immediateThumbnailData) { - if let image = UIImage(data: decodedData)?.precomposed() { - subscriber.putNext((image, false)) + if let thumbnailImage = UIImage(data: decodedData)?.precomposed() { + let thumbnailContextSize = CGSize(width: thumbnailImage.size.width * 6.0, height: thumbnailImage.size.height * 6.0) + guard let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) else { + subscriber.putNext((thumbnailImage, false)) + return + } + thumbnailContext.withFlippedContext { c in + if let image = thumbnailImage.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + if let blurredThumbnailImage = thumbnailContext.generateImage() { + subscriber.putNext((blurredThumbnailImage, false)) + } else { + subscriber.putNext((thumbnailImage, false)) + } } } } @@ -238,8 +256,25 @@ public func chatControllerBackgroundImageSignal(wallpaper: TelegramWallpaper, me } else if !didOutputBlurred { didOutputBlurred = true if let immediateThumbnailData = file.file.immediateThumbnailData, let decodedData = decodeTinyThumbnail(data: immediateThumbnailData) { - if let image = UIImage(data: decodedData)?.precomposed() { - subscriber.putNext((image, false)) + if let thumbnailImage = UIImage(data: decodedData)?.precomposed() { + let thumbnailContextSize = CGSize(width: thumbnailImage.size.width * 6.0, height: thumbnailImage.size.height * 6.0) + guard let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) else { + subscriber.putNext((thumbnailImage, false)) + return + } + thumbnailContext.withFlippedContext { c in + if let image = thumbnailImage.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + if let blurredThumbnailImage = thumbnailContext.generateImage() { + subscriber.putNext((blurredThumbnailImage, false)) + } else { + subscriber.putNext((thumbnailImage, false)) + } } } } diff --git a/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/Contents.json new file mode 100644 index 00000000000..5d62e000603 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "heart_18.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/heart_18.pdf b/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/heart_18.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d65614e11f2a844cab8d857d15be05683013806c GIT binary patch literal 1251 zcmZXUO>f&U42JLe6}$v!50yp9`T~jqYqAc*HY{^@D|YZ4HO zcUOht!!;~#=WkWV{xIZX7$By}MVh5=k{hXM8A$F>=So1eXKC5nNmtd(aAk)*htIi$ zJu$633O@!TX1KM|`wl1F` z%UzW9acf#fBIuQs4iGtNB|L3G?u6H*=Q%2GDC`kcjC7pRIYmqB$R6cS(oqDpG>ROL znNkE{l@rmjrb>CO2R#(dYY92IlH_Zqrno_4#b9YPLYBD^O5z-*5+9RDXqIeZsV0(3 z?TyZlh11+R>-|Z6HTO;#lh03qsw^p!gXC~hug|k$5A2@R>>G;>Id$^gw3wJZl2+8^g%6{azr~S_J6`^aM z+A4GquTC+_Et|RzHARf~W`gUoB?eTF0nKh#MCsOLpLQFv3NeEMrZ~&yiT2_f{~eK; zk~vX|33e&qG4W;DmygZ%bn9~)?wJ6~4%ak3!Dy{C5FQ6oRpNj;Aw@nnZYdJ0{e(9!xgUN< RW!n$wppc#|7Vp2_yZ~s44`ToT literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/Contents.json new file mode 100644 index 00000000000..962ddea2999 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "edit.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/edit.pdf b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/edit.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0259952bc5ec5a89fc66e92dfb0338a246eee097 GIT binary patch literal 2089 zcmZvdO>fjN5Qgvm6?3W7B9X-Ta2%s8 z_r7u4cbBla4SzL*`lWP~@B_*6@><^HTN&vxNz)Pxy(D=K&Cz>`nY;{5tMbTZ6dAOM zOi@{z!ci4bYHe1T02YxkiyeMU%L+UyBAc>ngRYBRM)4}0!a-@DC8R!$P#yYH5lICB zODPe>Y}ATor7m zk_?39%jPQjM3Dj_^Ao0n!V+213UY=Vyrq{!N;zmGA(Gj7m~k^Wk#!K@DM;zLI@2dA;rj0yx3%R9yRK5QA}we6 z7+pmz2y^bZKwhx5Wbp7>No; zEXB%lB%hG5Fo8HDiH0t?ooR3Zq!blyQF6=&o#yuGu)>LWhG}HPp88F27!D}Qk;Lpv z#}!5lOPn;hhL7t0m1@-&MW_0FWH0AC9p%%xuk+l>v7ZaZ>mYv9w%s_u-8Z}*@QJ_v z`RO3OsUO@7KDph#zU-di&i~U+bHUR&`_J1FY3qjWsc+l>bOCMWaGS0jU5gp%_F3Rf z-6I1$78nJ06d}30;t%^BmI~Ct7M47l?g@SMi~0v7K_s(^L=6s-&^NxR$NJDctX-db zcdrsC)pW+tUob_H6@YktG|G=2Xa!|h=L+c}voDar>8!2FmyR~^j$>c%53X0Vz4uo* uj?lasffum1s$gR?#W literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 6bf258998de..cd6de0c56db 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -858,9 +858,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.chatDisplayNode.dismissInput() let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true)) - wallpaperPreviewController.apply = { [weak wallpaperPreviewController] entry, options, _, _ in + wallpaperPreviewController.apply = { [weak wallpaperPreviewController] entry, options, _, _, _ in if case let .wallpaper(wallpaper, _) = entry, case let .file(file) = wallpaper, !file.isPattern && options.contains(.blur) { - uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: entry, mode: options, cropRect: nil, brightness: nil, peerId: message.id.peerId, completion: { + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: entry, mode: options, editedImage: nil, cropRect: nil, brightness: nil, peerId: message.id.peerId, completion: { wallpaperPreviewController?.dismiss() }) } else { @@ -1096,7 +1096,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { - legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { + legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { transitionCompletion() }, getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView() @@ -3970,7 +3970,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in + legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView() }, sendMessagesWithSignals: { [weak self] signals, _, _ in if let strongSelf = self { @@ -5830,8 +5830,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let themeEmoticon: Signal = self.chatThemeEmoticonPromise.get() |> distinctUntilChanged + + let uploadingChatWallpaper: Signal + if let peerId = self.chatLocation.peerId { + uploadingChatWallpaper = self.context.account.pendingPeerMediaUploadManager.uploadingPeerMedia + |> map { uploadingPeerMedia -> TelegramWallpaper? in + if let item = uploadingPeerMedia[peerId], case let .wallpaper(wallpaper) = item.content { + return wallpaper + } else { + return nil + } + } + |> distinctUntilChanged + } else { + uploadingChatWallpaper = .single(nil) + } - let chatWallpaper: Signal = self.chatWallpaperPromise.get() + let chatWallpaper: Signal = combineLatest(self.chatWallpaperPromise.get(), uploadingChatWallpaper) + |> map { chatWallpaper, uploadingChatWallpaper in + return uploadingChatWallpaper ?? chatWallpaper + } |> distinctUntilChanged let themeSettings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) @@ -18615,9 +18633,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) controller.navigationPresentation = .modal - controller.apply = { [weak self] wallpaper, options, cropRect, brightness in + controller.apply = { [weak self] wallpaper, options, editedImage, cropRect, brightness in if let strongSelf = self { - uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, brightness: brightness, peerId: peerId, completion: { + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: peerId, completion: { dismissControllers() }) } diff --git a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift index 213d5c27896..cec115a6820 100644 --- a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift @@ -46,6 +46,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode self.subtitleNode.displaysAsynchronously = false self.imageNode = TransformImageNode() + self.imageNode.contentAnimations = [.subsequentUpdates] self.buttonNode = HighlightTrackingButtonNode() self.buttonNode.clipsToBounds = true diff --git a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift index 589677da9a9..2423b084551 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -16,12 +16,16 @@ import PhotoResources import WallpaperResources import Markdown import RadialStatusNode +import ComponentFlow +import AudioTranscriptionPendingIndicatorComponent class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? private let mediaBackgroundNode: NavigationBackgroundNode private let subtitleNode: TextNode + private let progressNode: ImmediateTextNode private let imageNode: TransformImageNode + private var transcriptionPendingIndicator: ComponentHostView? private var statusOverlayNode: ASDisplayNode private var statusNode: RadialStatusNode @@ -43,7 +47,12 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { self.subtitleNode.isUserInteractionEnabled = false self.subtitleNode.displaysAsynchronously = false + self.progressNode = ImmediateTextNode() + self.progressNode.isUserInteractionEnabled = false + self.progressNode.displaysAsynchronously = false + self.imageNode = TransformImageNode() + self.imageNode.contentAnimations = [.subsequentUpdates] self.buttonNode = HighlightTrackingButtonNode() self.buttonNode.clipsToBounds = true @@ -66,6 +75,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.mediaBackgroundNode) self.addSubnode(self.subtitleNode) + self.addSubnode(self.progressNode) self.addSubnode(self.imageNode) self.addSubnode(self.buttonNode) @@ -172,14 +182,25 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { } private func updateProgress(_ progress: Float?) { + guard let item = self.item else { + return + } 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) + + let primaryTextColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText + self.progressNode.attributedText = NSAttributedString(string: "\(Int(progress * 100.0))%", font: Font.semibold(13.0), textColor: primaryTextColor, paragraphAlignment: .center) + let progressSize = self.progressNode.updateLayout(CGSize(width: 100.0, height: 100.0)) + let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(self.subtitleNode.frame.midX - progressSize.width / 2.0), y: self.subtitleNode.frame.maxY + 1.0), size: progressSize) + self.progressNode.isHidden = false + self.progressNode.frame = progressFrame } else { self.statusNode.transitionToState(.none) transition.updateAlpha(node: self.statusOverlayNode, alpha: 0.0) + self.progressNode.isHidden = true } } @@ -218,8 +239,14 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { let peerName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0).compactDisplayTitle } ?? "" let text: String + var displayTrailingAnimatedDots = false if fromYou { - text = item.presentationData.strings.Notification_YouChangedWallpaper + if item.message.id.namespace == Namespaces.Message.Local { + text = item.presentationData.strings.Notification_YouChangingWallpaper + displayTrailingAnimatedDots = true + } else { + text = item.presentationData.strings.Notification_YouChangedWallpaper + } } else { text = item.presentationData.strings.Notification_ChangedWallpaper(peerName).string } @@ -227,15 +254,24 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor) let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor) - let subtitle = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in + var subtitle = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) + if displayTrailingAnimatedDots { + let modifiedString = NSMutableAttributedString(attributedString: subtitle) + modifiedString.append(NSAttributedString(string: "...", font: Font.regular(13.0), textColor: .clear)) + subtitle = modifiedString + } let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_Wallpaper_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 140.0 + (fromYou ? 0.0 : 42.0)) + var textHeight = subtitleLayout.size.height + if displayTrailingAnimatedDots { + textHeight += subtitleLayout.size.height + } + let backgroundSize = CGSize(width: width, height: textHeight + 140.0 + (fromYou ? 0.0 : 42.0)) return (backgroundSize.width, { boundingWidth in return (backgroundSize, { [weak self] animation, synchronousLoads, _ in @@ -324,6 +360,34 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 127.0), size: subtitleLayout.size) strongSelf.subtitleNode.frame = subtitleFrame + if displayTrailingAnimatedDots { + let transcriptionPendingIndicator: ComponentHostView + if let current = strongSelf.transcriptionPendingIndicator { + transcriptionPendingIndicator = current + } else { + transcriptionPendingIndicator = ComponentHostView() + strongSelf.transcriptionPendingIndicator = transcriptionPendingIndicator + strongSelf.view.addSubview(transcriptionPendingIndicator) + } + + let indicatorComponent: AnyComponent + indicatorComponent = AnyComponent(AudioTranscriptionPendingLottieIndicatorComponent(color: primaryTextColor, font: Font.regular(13.0))) + + let indicatorSize = transcriptionPendingIndicator.update( + transition: .immediate, + component: indicatorComponent, + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + + transcriptionPendingIndicator.frame = CGRect(origin: CGPoint(x: strongSelf.subtitleNode.frame.midX + subtitleLayout.trailingLineWidth / 2.0 - indicatorSize.width + 2.0 - UIScreenPixel, y: strongSelf.subtitleNode.frame.maxY - indicatorSize.height - 3.0 - UIScreenPixel), size: indicatorSize) + } else { + if let transcriptionPendingIndicator = strongSelf.transcriptionPendingIndicator { + strongSelf.transcriptionPendingIndicator = nil + transcriptionPendingIndicator.removeFromSuperview() + } + } + let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: subtitleFrame.maxY + 18.0), size: buttonTitleLayout.size) strongSelf.buttonTitleNode.frame = buttonTitleFrame diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 035a1566006..1ca1e7719e0 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -20,27 +20,7 @@ import TooltipUI import AnimatedStickerNode import TelegramAnimatedStickerNode import ShimmerEffect - -private func closeButtonImage(theme: PresentationTheme) -> UIImage? { - return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(UIColor(rgb: 0x808084, alpha: 0.1).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - - context.setLineWidth(2.0) - context.setLineCap(.round) - context.setStrokeColor(theme.actionSheet.inputClearButtonColor.cgColor) - - context.move(to: CGPoint(x: 10.0, y: 10.0)) - context.addLine(to: CGPoint(x: 20.0, y: 20.0)) - context.strokePath() - - context.move(to: CGPoint(x: 20.0, y: 10.0)) - context.addLine(to: CGPoint(x: 10.0, y: 20.0)) - context.strokePath() - }) -} +import WebUI private struct ThemeSettingsThemeEntry: Comparable, Identifiable { let index: Int @@ -745,7 +725,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega private let contentBackgroundNode: ASDisplayNode private let titleNode: ASTextNode private let textNode: ImmediateTextNode - private let cancelButton: HighlightableButtonNode + private let cancelButtonNode: WebAppCancelButtonNode private let switchThemeButton: HighlightTrackingButtonNode private let animationContainerNode: ASDisplayNode private var animationNode: AnimationNode @@ -830,13 +810,13 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.contentBackgroundNode.backgroundColor = backgroundColor self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Title, font: Font.semibold(16.0), textColor: textColor) + self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Title, font: Font.semibold(17.0), textColor: textColor) self.textNode = ImmediateTextNode() - self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Subtitle(peerName).string, font: Font.regular(12.0), textColor: secondaryTextColor) + self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Subtitle(peerName).string, font: Font.regular(15.0), textColor: secondaryTextColor) + self.textNode.isHidden = true - self.cancelButton = HighlightableButtonNode() - self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) + self.cancelButtonNode = WebAppCancelButtonNode(theme: self.presentationData.theme, strings: self.presentationData.strings) self.switchThemeButton = HighlightTrackingButtonNode() self.animationContainerNode = ASDisplayNode() @@ -845,7 +825,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.animationNode = AnimationNode(animation: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0) self.animationNode.isUserInteractionEnabled = false - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) + self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 11.0, gloss: false) self.otherButton = HighlightableButtonNode() @@ -872,7 +852,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.backgroundNode.addSubnode(self.effectNode) self.backgroundNode.addSubnode(self.contentBackgroundNode) self.contentContainerNode.addSubnode(self.titleNode) - self.contentContainerNode.addSubnode(self.textNode) + self.buttonsContentContainerNode.addSubnode(self.textNode) self.buttonsContentContainerNode.addSubnode(self.doneButton) self.buttonsContentContainerNode.addSubnode(self.otherButton) @@ -880,10 +860,10 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.animationContainerNode.addSubnode(self.animationNode) self.topContentContainerNode.addSubnode(self.switchThemeButton) self.topContentContainerNode.addSubnode(self.listNode) - self.topContentContainerNode.addSubnode(self.cancelButton) + self.topContentContainerNode.addSubnode(self.cancelButtonNode) self.switchThemeButton.addTarget(self, action: #selector(self.switchThemePressed), forControlEvents: .touchUpInside) - self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) + self.cancelButtonNode.buttonNode.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) self.doneButton.pressed = { [weak self] in if let strongSelf = self { strongSelf.doneButton.isUserInteractionEnabled = false @@ -970,6 +950,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } } } + + self.updateCancelButton() } private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) { @@ -1018,6 +1000,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega UIView.transition(with: self.buttonsContentContainerNode.view, duration: ChatThemeScreen.themeCrossfadeDuration, options: [.transitionCrossDissolve, .curveLinear]) { self.updateButtons() } + self.updateCancelButton() self.skipButtonsUpdate = false self.themeSelectionsCount += 1 @@ -1027,23 +1010,17 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } private func updateButtons() { - let canResetWallpaper = self.controller?.canResetWallpaper ?? false - let doneButtonTitle: String - let otherButtonTitle: String var accentButtonTheme = true - var destructiveOtherButton = false + var otherIsEnabled = false if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji { doneButtonTitle = self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper - otherButtonTitle = canResetWallpaper ? self.presentationData.strings.Conversation_Theme_ResetWallpaper : self.presentationData.strings.Common_Cancel + otherIsEnabled = self.controller?.canResetWallpaper == true accentButtonTheme = false - destructiveOtherButton = canResetWallpaper } else if self.selectedEmoticon == nil && self.initiallySelectedEmoticon != nil { doneButtonTitle = self.presentationData.strings.Conversation_Theme_Reset - otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions } else { - doneButtonTitle = self.presentationData.strings.Conversation_Theme_ApplyBackground - otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions + doneButtonTitle = self.presentationData.strings.Conversation_Theme_Apply } let buttonTheme: SolidRoundedButtonTheme @@ -1058,7 +1035,25 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } self.doneButton.updateTheme(buttonTheme) - self.otherButton.setTitle(otherButtonTitle, with: Font.regular(17.0), with: destructiveOtherButton ? self.presentationData.theme.actionSheet.destructiveActionTextColor : self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) + self.otherButton.setTitle(self.presentationData.strings.Conversation_Theme_ResetWallpaper, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.destructiveActionTextColor, for: .normal) + self.otherButton.isHidden = !otherIsEnabled + self.textNode.isHidden = !accentButtonTheme || self.controller?.canResetWallpaper == false + + if let (layout, navigationBarHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } + + private func updateCancelButton() { + var cancelButtonState: WebAppCancelButtonNode.State = .cancel + if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji { + + } else if self.selectedEmoticon == nil && self.initiallySelectedEmoticon != nil { + cancelButtonState = .back + } else { + cancelButtonState = .back + } + self.cancelButtonNode.setState(cancelButtonState, animated: true) } private var switchThemeIconAnimator: DisplayLinkAnimator? @@ -1069,14 +1064,14 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let previousTheme = self.presentationData.theme self.presentationData = presentationData - self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.semibold(16.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) - self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(12.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.semibold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) + self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor) if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) } - self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) + self.cancelButtonNode.theme = presentationData.theme let previousIconColors = iconColors(theme: previousTheme) let newIconColors = iconColors(theme: self.presentationData.theme) @@ -1113,7 +1108,11 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } @objc func cancelButtonPressed() { - self.cancel?() + if self.cancelButtonNode.state == .back { + self.setEmoticon(self.initiallySelectedEmoticon) + } else { + self.cancel?() + } } @objc func otherButtonPressed() { @@ -1317,7 +1316,10 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let bottomInset: CGFloat = 10.0 + cleanInsets.bottom let titleHeight: CGFloat = 54.0 - let contentHeight = titleHeight + bottomInset + 188.0 + 50.0 + var contentHeight = titleHeight + bottomInset + 168.0 + if self.controller?.canResetWallpaper == true { + contentHeight += 50.0 + } let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) @@ -1336,29 +1338,38 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) let titleSize = self.titleNode.measure(CGSize(width: width - 90.0, height: titleHeight)) - let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 11.0 + UIScreenPixel), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 18.0 + UIScreenPixel), size: titleSize) transition.updateFrame(node: self.titleNode, frame: titleFrame) - let textSize = self.textNode.updateLayout(CGSize(width: width - 90.0, height: titleHeight)) - let textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: 31.0), size: textSize) - transition.updateFrame(node: self.textNode, frame: textFrame) - let switchThemeSize = CGSize(width: 44.0, height: 44.0) - let switchThemeFrame = CGRect(origin: CGPoint(x: 3.0, y: 6.0), size: switchThemeSize) + let switchThemeFrame = CGRect(origin: CGPoint(x: contentFrame.width - switchThemeSize.width - 3.0, y: 6.0), size: switchThemeSize) transition.updateFrame(node: self.switchThemeButton, frame: switchThemeFrame) transition.updateFrame(node: self.animationContainerNode, frame: switchThemeFrame.insetBy(dx: 9.0, dy: 9.0)) transition.updateFrameAsPositionAndBounds(node: self.animationNode, frame: CGRect(origin: .zero, size: self.animationContainerNode.frame.size)) - let cancelSize = CGSize(width: 44.0, height: 44.0) - let cancelFrame = CGRect(origin: CGPoint(x: contentFrame.width - cancelSize.width - 3.0, y: 6.0), size: cancelSize) - transition.updateFrame(node: self.cancelButton, frame: cancelFrame) - + let cancelSize = self.cancelButtonNode.calculateSizeThatFits(CGSize(width: layout.size.width, height: 56.0)) + let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: cancelSize) + transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame) + let buttonInset: CGFloat = 16.0 let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) - transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - 50.0 - insets.bottom - 6.0, width: contentFrame.width, height: doneButtonHeight)) + var doneY = contentHeight - doneButtonHeight - 2.0 - insets.bottom + if self.controller?.canResetWallpaper == true { + doneY = contentHeight - doneButtonHeight - 52.0 - insets.bottom + } + transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: doneY, width: contentFrame.width, height: doneButtonHeight)) + + let otherButtonSize = self.otherButton.measure(CGSize(width: contentFrame.width - buttonInset * 2.0, height: .greatestFiniteMagnitude)) + self.otherButton.frame = CGRect(origin: CGPoint(x: floor((contentFrame.width - otherButtonSize.width) / 2.0), y: contentHeight - otherButtonSize.height - insets.bottom - 15.0), size: otherButtonSize) - let colorButtonSize = self.otherButton.measure(CGSize(width: contentFrame.width - buttonInset * 2.0, height: .greatestFiniteMagnitude)) - transition.updateFrame(node: self.otherButton, frame: CGRect(origin: CGPoint(x: floor((contentFrame.width - colorButtonSize.width) / 2.0), y: contentHeight - colorButtonSize.height - insets.bottom - 6.0 - 9.0), size: colorButtonSize)) + let textSize = self.textNode.updateLayout(CGSize(width: width - 90.0, height: titleHeight)) + let textFrame: CGRect + if self.controller?.canResetWallpaper == true { + textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: contentHeight - textSize.height - insets.bottom - 17.0), size: textSize) + } else { + textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: contentHeight - textSize.height - insets.bottom - 15.0), size: textSize) + } + transition.updateFrame(node: self.textNode, frame: textFrame) transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) transition.updateFrame(node: self.topContentContainerNode, frame: contentContainerFrame) @@ -1371,7 +1382,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let contentSize = CGSize(width: contentFrame.width, height: 120.0) self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width) - self.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + titleHeight + 6.0) + self.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + titleHeight - 4.0) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 048961a0af8..a095843d44f 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3906,6 +3906,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate var previousAbout: String? var currentAbout: String? + var previousIsBlocked: Bool? + var currentIsBlocked: Bool? + var previousPhotoIsPersonal: Bool? var currentPhotoIsPersonal: Bool? if let previousUser = previousData?.peer as? TelegramUser { @@ -3932,6 +3935,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate currentVideoCallsAvailable = cachedData.videoCallsAvailable previousAbout = previousCachedData.about currentAbout = cachedData.about + previousIsBlocked = previousCachedData.isBlocked + currentIsBlocked = cachedData.isBlocked } if self.isSettings { @@ -3954,6 +3959,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate if let previousPhotoIsPersonal, let currentPhotoIsPersonal, previousPhotoIsPersonal != currentPhotoIsPersonal { infoUpdated = true } + if let previousIsBlocked, let currentIsBlocked, previousIsBlocked != currentIsBlocked { + infoUpdated = true + } self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: self.didSetReady && (membersUpdated || infoUpdated) ? .animated(duration: 0.3, curve: .spring) : .immediate) } } @@ -4091,7 +4099,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { - legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: message.associatedThreadInfo?.title, media: mediaReference, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { + legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: message.associatedThreadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { transitionCompletion() }, getCaptionPanelView: { return nil @@ -5396,6 +5404,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate case .leave: self.openLeavePeer(delete: false) case .stop: + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: self.presentationData.strings.PeerInfo_BotBlockedTitle, text: self.presentationData.strings.PeerInfo_BotBlockedText, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) self.updateBlocked(block: true) } } diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 230b208e501..7076506512d 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -1030,6 +1030,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } else { strongSelf.blurredBackgroundContents = nil } + strongSelf.updateBubbles() strongSelf._isReady.set(true) })) } diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index b85cdf200d3..41c46e91b52 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -258,7 +258,7 @@ public func wallpaperImage(account: Account, accountManager: AccountManager Date: Tue, 11 Apr 2023 23:07:06 +0400 Subject: [PATCH 41/57] Fix chat list icon layout --- submodules/ChatListUI/Sources/Node/ChatListItem.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index d9d620a4242..89de56c4206 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -3312,6 +3312,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleLayout.trailingLineWidth + 3.0 + titleOffset + let lastLineRect: CGRect + if let rect = titleLayout.linesRects().last { + lastLineRect = CGRect(origin: CGPoint(x: 0.0, y: titleLayout.size.height - rect.height - 2.0), size: CGSize(width: rect.width, height: rect.height + 2.0)) + } else { + lastLineRect = CGRect(origin: CGPoint(), size: titleLayout.size) + } if let currentCredibilityIconContent = currentCredibilityIconContent { let credibilityIconView: ComponentHostView @@ -3339,7 +3345,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { environment: {}, containerSize: CGSize(width: 20.0, height: 20.0) ) - transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0) - UIScreenPixel), size: iconSize)) + transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - iconSize.height / 2.0) - UIScreenPixel), size: iconSize)) nextTitleIconOrigin += credibilityIconView.bounds.width + 4.0 } else if let credibilityIconView = strongSelf.credibilityIconView { strongSelf.credibilityIconView = nil @@ -3349,7 +3355,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let currentMutedIconImage = currentMutedIconImage { strongSelf.mutedIconNode.image = currentMutedIconImage strongSelf.mutedIconNode.isHidden = false - transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.midY - currentMutedIconImage.size.height / 2.0)), size: currentMutedIconImage.size)) + transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - currentMutedIconImage.size.height / 2.0)), size: currentMutedIconImage.size)) nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0 } else { strongSelf.mutedIconNode.image = nil From c206824feece2eba80526981958833c4aad9a010 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 11 Apr 2023 23:10:30 +0400 Subject: [PATCH 42/57] Add empty chat list contacts --- .../Sources/ChatListController.swift | 30 ++ .../Sources/Node/ChatListEmptyInfoItem.swift | 278 ++++++++++++++++++ .../Sources/Node/ChatListNode.swift | 149 +++++++++- .../Sources/Node/ChatListNodeEntries.swift | 133 ++++++++- .../Display/Source/TooltipController.swift | 5 +- .../Source/TooltipControllerNode.swift | 7 +- .../Sources/ListSectionHeaderNode.swift | 24 +- 7 files changed, 612 insertions(+), 14 deletions(-) create mode 100644 submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 0f02abfe96c..a571f136030 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2000,6 +2000,36 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let _ = value }) }) + + Queue.mainQueue().after(2.0, { [weak self] in + guard let self else { + return + } + //TODO:generalize + var hasEmptyMark = false + self.chatListDisplayNode.mainContainerNode.currentItemNode.forEachItemNode { itemNode in + if itemNode is ChatListSectionHeaderNode { + hasEmptyMark = true + } + } + if hasEmptyMark { + if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { + if let rightButtonView = componentView.rightButtonView { + let absoluteFrame = rightButtonView.convert(rightButtonView.bounds, to: self.view) + //TODO:localize + let text: String = "Send a message or\nstart a group here." + + let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0)) + self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in + guard let self else { + return nil + } + return (self.displayNode, absoluteFrame.insetBy(dx: 4.0, dy: 8.0).offsetBy(dx: 4.0, dy: -1.0)) + })) + } + } + } + }) } self.chatListDisplayNode.mainContainerNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in diff --git a/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift new file mode 100644 index 00000000000..78f28d14009 --- /dev/null +++ b/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift @@ -0,0 +1,278 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Postbox +import Display +import SwiftSignalKit +import TelegramPresentationData +import ListSectionHeaderNode +import AppBundle +import AnimatedStickerNode +import TelegramAnimatedStickerNode + +class ChatListEmptyInfoItem: ListViewItem { + let theme: PresentationTheme + let strings: PresentationStrings + + let selectable: Bool = false + + init(theme: PresentationTheme, strings: PresentationStrings) { + self.theme = theme + self.strings = strings + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatListEmptyInfoItemNode() + + let (nodeLayout, apply) = node.asyncLayout()(self, params, false) + + node.insets = nodeLayout.insets + node.contentSize = nodeLayout.contentSize + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in + apply() + }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + assert(node() is ChatListEmptyInfoItemNode) + if let nodeValue = node() as? ChatListEmptyInfoItemNode { + + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params, nextItem == nil) + Queue.mainQueue().async { + completion(nodeLayout, { _ in + apply() + }) + } + } + } + } + } +} + +class ChatListEmptyInfoItemNode: ListViewItemNode { + private var item: ChatListEmptyInfoItem? + + private let animationNode: AnimatedStickerNode + private let textNode: TextNode + + override var visibility: ListViewItemNodeVisibility { + didSet { + let wasVisible = self.visibilityStatus + let isVisible: Bool + switch self.visibility { + case let .visible(fraction, _): + isVisible = fraction > 0.2 + case .none: + isVisible = false + } + if wasVisible != isVisible { + self.visibilityStatus = isVisible + } + } + } + + private var visibilityStatus: Bool = false { + didSet { + if self.visibilityStatus != oldValue { + self.animationNode.visibility = self.visibilityStatus + } + } + } + + required init() { + self.animationNode = DefaultAnimatedStickerNodeImpl() + self.textNode = TextNode() + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.animationNode) + self.addSubnode(self.textNode) + } + + override func didLoad() { + super.didLoad() + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + let layout = self.asyncLayout() + let (_, apply) = layout(item as! ChatListEmptyInfoItem, params, nextItem == nil) + apply() + } + + func asyncLayout() -> (_ item: ChatListEmptyInfoItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) { + let makeTextLayout = TextNode.asyncLayout(self.textNode) + + return { item, params, last in + let baseWidth = params.width - params.leftInset - params.rightInset + + let topInset: CGFloat = 8.0 + let textSpacing: CGFloat = 27.0 + let bottomInset: CGFloat = 24.0 + let animationHeight: CGFloat = 140.0 + + let string = NSMutableAttributedString(string: item.strings.ChatList_EmptyChatList, font: Font.semibold(17.0), textColor: item.theme.list.itemPrimaryTextColor) + + let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: string, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: .greatestFiniteMagnitude), alignment: .center)) + + let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: topInset + animationHeight + textSpacing + textLayout.0.size.height + bottomInset), insets: UIEdgeInsets()) + + return (layout, { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.item = item + + var topOffset: CGFloat = topInset + + let animationFrame = CGRect(origin: CGPoint(x: floor((params.width - animationHeight) * 0.5), y: topOffset), size: CGSize(width: animationHeight, height: animationHeight)) + if strongSelf.animationNode.bounds.isEmpty { + strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ChatListEmpty"), width: 248, height: 248, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + } + strongSelf.animationNode.frame = animationFrame + topOffset += animationHeight + textSpacing + + let _ = textLayout.1() + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.0.size.width) * 0.5), y: topOffset), size: textLayout.0.size) + + strongSelf.contentSize = layout.contentSize + strongSelf.insets = layout.insets + }) + } + } +} + +class ChatListSectionHeaderItem: ListViewItem { + let theme: PresentationTheme + let strings: PresentationStrings + let hide: (() -> Void)? + + let selectable: Bool = false + + init(theme: PresentationTheme, strings: PresentationStrings, hide: (() -> Void)?) { + self.theme = theme + self.strings = strings + self.hide = hide + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatListSectionHeaderNode() + + let (nodeLayout, apply) = node.asyncLayout()(self, params, false) + + node.insets = nodeLayout.insets + node.contentSize = nodeLayout.contentSize + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in + apply() + }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + assert(node() is ChatListSectionHeaderNode) + if let nodeValue = node() as? ChatListSectionHeaderNode { + + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params, nextItem == nil) + Queue.mainQueue().async { + completion(nodeLayout, { _ in + apply() + }) + } + } + } + } + } +} + +class ChatListSectionHeaderNode: ListViewItemNode { + private var item: ChatListSectionHeaderItem? + + private var headerNode: ListSectionHeaderNode? + + required init() { + super.init(layerBacked: false, dynamicBounce: false) + + self.zPosition = 1.0 + } + + override func didLoad() { + super.didLoad() + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + let layout = self.asyncLayout() + let (_, apply) = layout(item as! ChatListSectionHeaderItem, params, nextItem == nil) + apply() + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let headerNode = self.headerNode { + if let result = headerNode.view.hitTest(self.view.convert(point, to: headerNode.view), with: event) { + return result + } + } + return nil + } + + func asyncLayout() -> (_ item: ChatListSectionHeaderItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) { + return { item, params, last in + let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 28.0), insets: UIEdgeInsets()) + + return (layout, { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.item = item + + let headerNode: ListSectionHeaderNode + if let current = strongSelf.headerNode { + headerNode = current + } else { + headerNode = ListSectionHeaderNode(theme: item.theme) + strongSelf.headerNode = headerNode + strongSelf.addSubnode(headerNode) + } + + //TODO:localize + headerNode.title = "YOUR CONTACTS ON TELEGRAM" + if item.hide != nil { + headerNode.action = "hide" + headerNode.actionType = .generic + headerNode.activateAction = { + guard let self else { + return + } + self.item?.hide?() + } + } else { + headerNode.action = nil + } + + headerNode.updateTheme(theme: item.theme) + headerNode.updateLayout(size: CGSize(width: params.width, height: layout.contentSize.height), leftInset: params.leftInset, rightInset: params.rightInset) + + strongSelf.contentSize = layout.contentSize + strongSelf.insets = layout.insets + }) + } + } +} + diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 24506af387b..6cdfc5f62ce 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -616,8 +616,44 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) + case let .ContactEntry(contactEntry): + let header: ChatListSearchItemHeader? = nil + + var status: ContactsPeerItemStatus = .none + status = .presence(contactEntry.presence, contactEntry.presentationData.dateTimeFormat) + + let presentationData = contactEntry.presentationData + + let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer) + + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( + presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), + sortOrder: presentationData.nameSortOrder, + displayOrder: presentationData.nameDisplayOrder, + context: context, + peerMode: .generalSearch, + peer: peerContent, + status: status, + enabled: true, + selection: .none, + editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), + index: nil, + header: header, + action: { _ in + nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil) + }, + disabledAction: nil, + animationCache: nodeInteraction.animationCache, + animationRenderer: nodeInteraction.animationRenderer + ), directionHint: entry.directionHint) case let .ArchiveIntro(presentationData): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) + case let .EmptyIntro(presentationData): + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) + case let .SectionHeader(presentationData, displayHide): + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? { + hideChatListContacts(context: context) + } : nil), directionHint: entry.directionHint) case let .Notice(presentationData, notice): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in switch action { @@ -881,8 +917,44 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) + case let .ContactEntry(contactEntry): + let header: ChatListSearchItemHeader? = nil + + var status: ContactsPeerItemStatus = .none + status = .presence(contactEntry.presence, contactEntry.presentationData.dateTimeFormat) + + let presentationData = contactEntry.presentationData + + let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer) + + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( + presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), + sortOrder: presentationData.nameSortOrder, + displayOrder: presentationData.nameDisplayOrder, + context: context, + peerMode: .generalSearch, + peer: peerContent, + status: status, + enabled: true, + selection: .none, + editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), + index: nil, + header: header, + action: { _ in + nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil) + }, + disabledAction: nil, + animationCache: nodeInteraction.animationCache, + animationRenderer: nodeInteraction.animationRenderer + ), directionHint: entry.directionHint) case let .ArchiveIntro(presentationData): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) + case let .EmptyIntro(presentationData): + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint) + case let .SectionHeader(presentationData, displayHide): + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? { + hideChatListContacts(context: context) + } : nil), directionHint: entry.directionHint) case let .Notice(presentationData, notice): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in switch action { @@ -1702,6 +1774,65 @@ public final class ChatListNode: ListView { let _ = self.enqueueTransition(value).start() })*/ + let contacts: Signal<[ChatListContactPeer], NoError> + if case .chatList(groupId: .root) = location, chatListFilter == nil { + contacts = ApplicationSpecificNotice.displayChatListContacts(accountManager: context.sharedContext.accountManager) + |> distinctUntilChanged + |> mapToSignal { value -> Signal<[ChatListContactPeer], NoError> in + if value { + return .single([]) + } + + return context.engine.messages.chatList(group: .root, count: 10) + |> map { chatList -> Bool in + if chatList.items.count >= 5 { + return true + } else { + return false + } + } + |> distinctUntilChanged + |> mapToSignal { hasChats -> Signal<[ChatListContactPeer], NoError> in + if hasChats { + return .single([]) + } + + return context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: true) + ) + |> mapToThrottled { next -> Signal in + return .single(next) + |> then( + .complete() + |> delay(5.0, queue: Queue.concurrentDefaultQueue()) + ) + } + |> map { contactList -> [ChatListContactPeer] in + var result: [ChatListContactPeer] = [] + for peer in contactList.peers { + if peer.id == context.account.peerId { + continue + } + result.append(ChatListContactPeer( + peer: peer, + presence: contactList.presences[peer.id] ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0) + )) + } + result.sort(by: { lhs, rhs in + if lhs.presence.status != rhs.presence.status { + return lhs.presence.status < rhs.presence.status + } else { + return lhs.peer.id < rhs.peer.id + } + }) + return result + } + } + } + } else { + contacts = .single([]) + } + let chatListNodeViewTransition = combineLatest( queue: viewProcessingQueue, hideArchivedFolderByDefault, @@ -1711,9 +1842,10 @@ public final class ChatListNode: ListView { savedMessagesPeer, chatListViewUpdate, self.chatFolderUpdates.get() |> distinctUntilChanged, - self.statePromise.get() + self.statePromise.get(), + contacts ) - |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, chatFolderUpdates, state) -> Signal in + |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, chatFolderUpdates, state, contacts) -> Signal in let (update, filter) = updateAndFilter let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault) @@ -1729,7 +1861,7 @@ public final class ChatListNode: ListView { notice = nil } - let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location) + let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location, contacts: contacts) var isEmpty = true var entries = rawEntries.filter { entry in switch entry { @@ -1974,6 +2106,9 @@ public final class ChatListNode: ListView { return false } } + case .ContactEntry: + isEmpty = false + return true case .GroupReferenceEntry: isEmpty = false return true @@ -2910,7 +3045,7 @@ public final class ChatListNode: ListView { var hasArchive = false loop: for entry in transition.chatListView.filteredEntries { switch entry { - case .GroupReferenceEntry, .HoleEntry, .PeerEntry: + case .GroupReferenceEntry, .HoleEntry, .PeerEntry, .ContactEntry: if case .GroupReferenceEntry = entry { hasArchive = true } else { @@ -2929,7 +3064,7 @@ public final class ChatListNode: ListView { } else { break loop } - case .ArchiveIntro, .Notice, .HeaderEntry, .AdditionalCategory: + case .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .HeaderEntry, .AdditionalCategory: break } } @@ -3660,3 +3795,7 @@ public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer { } } } + +func hideChatListContacts(context: AccountContext) { + let _ = ApplicationSpecificNotice.setDisplayChatListContacts(accountManager: context.sharedContext.accountManager).start() +} diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 8d9b8d8300d..43d34c20ade 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -12,7 +12,10 @@ enum ChatListNodeEntryId: Hashable { case PeerId(Int64) case ThreadId(Int64) case GroupId(EngineChatList.Group) + case ContactId(EnginePeer.Id) case ArchiveIntro + case EmptyIntro + case SectionHeader case Notice case additionalCategory(Int) } @@ -20,6 +23,8 @@ enum ChatListNodeEntryId: Hashable { enum ChatListNodeEntrySortIndex: Comparable { case index(EngineChatList.Item.Index) case additionalCategory(Int) + case sectionHeader + case contact(id: EnginePeer.Id, presence: EnginePeer.Presence) static func <(lhs: ChatListNodeEntrySortIndex, rhs: ChatListNodeEntrySortIndex) -> Bool { switch lhs { @@ -29,6 +34,10 @@ enum ChatListNodeEntrySortIndex: Comparable { return lhsIndex < rhsIndex case .additionalCategory: return false + case .sectionHeader: + return true + case .contact: + return true } case let .additionalCategory(lhsIndex): switch rhs { @@ -36,6 +45,30 @@ enum ChatListNodeEntrySortIndex: Comparable { return lhsIndex < rhsIndex case .index: return true + case .sectionHeader: + return true + case .contact: + return true + } + case .sectionHeader: + switch rhs { + case .additionalCategory, .index, .sectionHeader: + return false + case .contact: + return true + } + case let .contact(lhsId, lhsPresense): + switch rhs { + case .sectionHeader: + return false + case let .contact(rhsId, rhsPresense): + if lhsPresense != rhsPresense { + return rhsPresense.status > rhsPresense.status + } else { + return lhsId < rhsId + } + default: + return false } } } @@ -238,11 +271,39 @@ enum ChatListNodeEntry: Comparable, Identifiable { } } + struct ContactEntryData: Equatable { + var presentationData: ChatListPresentationData + var peer: EnginePeer + var presence: EnginePeer.Presence + + init(presentationData: ChatListPresentationData, peer: EnginePeer, presence: EnginePeer.Presence) { + self.presentationData = presentationData + self.peer = peer + self.presence = presence + } + + static func ==(lhs: ContactEntryData, rhs: ContactEntryData) -> Bool { + if lhs.presentationData !== rhs.presentationData { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.presence != rhs.presence { + return false + } + return true + } + } + case HeaderEntry case PeerEntry(PeerEntryData) case HoleEntry(EngineMessage.Index, theme: PresentationTheme) case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool) + case ContactEntry(ContactEntryData) case ArchiveIntro(presentationData: ChatListPresentationData) + case EmptyIntro(presentationData: ChatListPresentationData) + case SectionHeader(presentationData: ChatListPresentationData, displayHide: Bool) case Notice(presentationData: ChatListPresentationData, notice: ChatListNotice) case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData) @@ -256,8 +317,14 @@ enum ChatListNodeEntry: Comparable, Identifiable { return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex))) case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _): return .index(index) + case let .ContactEntry(contactEntry): + return .contact(id: contactEntry.peer.id, presence: contactEntry.presence) case .ArchiveIntro: return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor)) + case .EmptyIntro: + return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor)) + case .SectionHeader: + return .sectionHeader case .Notice: return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor)) case let .AdditionalCategory(index, _, _, _, _, _, _): @@ -280,8 +347,14 @@ enum ChatListNodeEntry: Comparable, Identifiable { return .Hole(Int64(holeIndex.id.id)) case let .GroupReferenceEntry(_, _, groupId, _, _, _, _, _, _): return .GroupId(groupId) + case let .ContactEntry(contactEntry): + return .ContactId(contactEntry.peer.id) case .ArchiveIntro: return .ArchiveIntro + case .EmptyIntro: + return .EmptyIntro + case .SectionHeader: + return .SectionHeader case .Notice: return .Notice case let .AdditionalCategory(_, id, _, _, _, _, _): @@ -347,6 +420,12 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { return false } + case let .ContactEntry(contactEntry): + if case .ContactEntry(contactEntry) = rhs { + return true + } else { + return false + } case let .ArchiveIntro(lhsPresentationData): if case let .ArchiveIntro(rhsPresentationData) = rhs { if lhsPresentationData !== rhsPresentationData { @@ -356,6 +435,27 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { return false } + case let .EmptyIntro(lhsPresentationData): + if case let .EmptyIntro(rhsPresentationData) = rhs { + if lhsPresentationData !== rhsPresentationData { + return false + } + return true + } else { + return false + } + case let .SectionHeader(lhsPresentationData, lhsDisplayHide): + if case let .SectionHeader(rhsPresentationData, rhsDisplayHide) = rhs { + if lhsPresentationData !== rhsPresentationData { + return false + } + if lhsDisplayHide != rhsDisplayHide { + return false + } + return true + } else { + return false + } case let .Notice(lhsPresentationData, lhsInfo): if case let .Notice(rhsPresentationData, rhsInfo) = rhs { if lhsPresentationData !== rhsPresentationData { @@ -407,9 +507,32 @@ private func offsetPinnedIndex(_ index: EngineChatList.Item.Index, offset: UInt1 } } -func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) { +struct ChatListContactPeer { + var peer: EnginePeer + var presence: EnginePeer.Presence + + init(peer: EnginePeer, presence: EnginePeer.Presence) { + self.peer = peer + self.presence = presence + } +} + +func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer]) -> (entries: [ChatListNodeEntry], loading: Bool) { var result: [ChatListNodeEntry] = [] + if !view.hasEarlier { + for contact in contacts { + result.append(.ContactEntry(ChatListNodeEntry.ContactEntryData( + presentationData: state.presentationData, + peer: contact.peer, + presence: contact.presence + ))) + } + if !contacts.isEmpty { + result.append(.SectionHeader(presentationData: state.presentationData, displayHide: !view.items.isEmpty)) + } + } + var pinnedIndexOffset: UInt16 = 0 if !view.hasLater, case .chatList = mode { @@ -668,6 +791,14 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState if displayArchiveIntro { result.append(.ArchiveIntro(presentationData: state.presentationData)) + } else if !contacts.isEmpty && !result.contains(where: { entry in + if case .PeerEntry = entry { + return true + } else { + return false + } + }) { + result.append(.EmptyIntro(presentationData: state.presentationData)) } if let notice { diff --git a/submodules/Display/Source/TooltipController.swift b/submodules/Display/Source/TooltipController.swift index 1ab86d59ef0..be5d52bff76 100644 --- a/submodules/Display/Source/TooltipController.swift +++ b/submodules/Display/Source/TooltipController.swift @@ -123,13 +123,14 @@ open class TooltipController: ViewController, StandalonePresentableController { private var timeoutTimer: SwiftSignalKit.Timer? private var padding: CGFloat + private var innerPadding: UIEdgeInsets private var layout: ContainerViewLayout? private var initialArrowOnBottom: Bool public var dismissed: ((Bool) -> Void)? - public init(content: TooltipControllerContent, baseFontSize: CGFloat, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0) { + public init(content: TooltipControllerContent, baseFontSize: CGFloat, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0, innerPadding: UIEdgeInsets = UIEdgeInsets()) { self.content = content self.baseFontSize = baseFontSize self.timeout = timeout @@ -138,6 +139,7 @@ open class TooltipController: ViewController, StandalonePresentableController { self.dismissImmediatelyOnLayoutUpdate = dismissImmediatelyOnLayoutUpdate self.initialArrowOnBottom = arrowOnBottom self.padding = padding + self.innerPadding = innerPadding super.init(navigationBarPresentationData: nil) @@ -157,6 +159,7 @@ open class TooltipController: ViewController, StandalonePresentableController { self?.dismiss(tappedInside: tappedInside) }, dismissByTapOutside: self.dismissByTapOutside, dismissByTapOutsideSource: self.dismissByTapOutsideSource) self.controllerNode.padding = self.padding + self.controllerNode.innerPadding = self.innerPadding self.controllerNode.arrowOnBottom = self.initialArrowOnBottom self.displayNodeDidLoad() } diff --git a/submodules/Display/Source/TooltipControllerNode.swift b/submodules/Display/Source/TooltipControllerNode.swift index d044cbc5f6b..7122720407b 100644 --- a/submodules/Display/Source/TooltipControllerNode.swift +++ b/submodules/Display/Source/TooltipControllerNode.swift @@ -20,6 +20,7 @@ final class TooltipControllerNode: ASDisplayNode { var arrowOnBottom: Bool = true var padding: CGFloat = 8.0 + var innerPadding: UIEdgeInsets = UIEdgeInsets() private var dismissedByTouchOutside = false private var dismissByTapOutsideSource = false @@ -98,14 +99,14 @@ final class TooltipControllerNode: ASDisplayNode { textSize.width = ceil(textSize.width / 2.0) * 2.0 textSize.height = ceil(textSize.height / 2.0) * 2.0 - contentSize = CGSize(width: imageSizeWithInset.width + textSize.width + 12.0, height: textSize.height + 34.0) + contentSize = CGSize(width: imageSizeWithInset.width + textSize.width + 12.0 + self.innerPadding.left + self.innerPadding.right, height: textSize.height + 34.0 + self.innerPadding.top + self.innerPadding.bottom) - let textFrame = CGRect(origin: CGPoint(x: 6.0 + imageSizeWithInset.width, y: 17.0), size: textSize) + let textFrame = CGRect(origin: CGPoint(x: 6.0 + self.innerPadding.left + imageSizeWithInset.width, y: 17.0 + self.innerPadding.top), size: textSize) if transition.isAnimated, textFrame.size != self.textNode.frame.size { transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: textFrame.minX - self.textNode.frame.minX, y: 0.0)) } - let imageFrame = CGRect(origin: CGPoint(x: 10.0, y: floor((contentSize.height - imageSize.height) / 2.0)), size: imageSize) + let imageFrame = CGRect(origin: CGPoint(x: self.innerPadding.left + 10.0, y: floor((contentSize.height - imageSize.height) / 2.0)), size: imageSize) self.imageNode.frame = imageFrame self.textNode.frame = textFrame } diff --git a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift index d737a6a77f0..bbd1942090e 100644 --- a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift +++ b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift @@ -5,7 +5,7 @@ import Display import TelegramPresentationData private let titleFont = Font.bold(13.0) -private let actionFont = Font.medium(13.0) +private let actionFont = Font.regular(13.0) public enum ListSectionHeaderActionType { case generic @@ -13,6 +13,7 @@ public enum ListSectionHeaderActionType { } public final class ListSectionHeaderNode: ASDisplayNode { + private let backgroundLayer: SimpleLayer private let label: ImmediateTextNode private var actionButtonLabel: ImmediateTextNode? private var actionButton: HighlightableButtonNode? @@ -87,16 +88,29 @@ public final class ListSectionHeaderNode: ASDisplayNode { public init(theme: PresentationTheme) { self.theme = theme + self.backgroundLayer = SimpleLayer() + self.label = ImmediateTextNode() self.label.isUserInteractionEnabled = false self.label.isAccessibilityElement = true super.init() - + self.layer.addSublayer(self.backgroundLayer) + self.addSubnode(self.label) - self.backgroundColor = theme.chatList.sectionHeaderFillColor + self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let actionButton = self.actionButton { + if actionButton.frame.contains(point) { + return actionButton.view + } + } + + return super.hitTest(point, with: event) } public func updateTheme(theme: PresentationTheme) { @@ -105,7 +119,7 @@ public final class ListSectionHeaderNode: ASDisplayNode { self.label.attributedText = NSAttributedString(string: self.title ?? "", font: titleFont, textColor: self.theme.chatList.sectionHeaderTextColor) - self.backgroundColor = theme.chatList.sectionHeaderFillColor + self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor if let action = self.action { self.actionButtonLabel?.attributedText = NSAttributedString(string: action, font: actionFont, textColor: self.theme.chatList.sectionHeaderTextColor) } @@ -126,6 +140,8 @@ public final class ListSectionHeaderNode: ASDisplayNode { actionButtonLabel.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize) actionButton.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize) } + + self.backgroundLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel)) } @objc private func actionButtonPressed() { From a0e9f2bbe4e6028534b3dffd8a2e2fe3a33c7914 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 11 Apr 2023 23:10:49 +0400 Subject: [PATCH 43/57] Resolve phone numbers in msg links --- .../TelegramUI/Sources/OpenResolvedUrl.swift | 11 +++++++++-- submodules/UrlHandling/Sources/UrlHandling.swift | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index e34778b835e..bf8b2a44f6e 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -324,7 +324,14 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur } }) } else { - let query = to.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789").inverted) + let _ = (context.engine.peers.resolvePeerByPhone(phone: to) + |> deliverOnMainQueue).start(next: { peer in + if let peer = peer { + context.sharedContext.applicationBindings.dismissNativeController() + continueWithPeer(peer.id) + } + }) + /*let query = to.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789").inverted) let _ = (context.account.postbox.searchContacts(query: query) |> deliverOnMainQueue).start(next: { (peers, _) in for case let peer as TelegramUser in peers { @@ -334,7 +341,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur break } } - }) + })*/ } } else { if let url = url, !url.isEmpty { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index aeefeafe424..ea4eccf27c7 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -194,6 +194,22 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { if let phone = phone, let hash = hash { return .cancelAccountReset(phone: phone, hash: hash) } + } else if peerName == "msg" { + var url: String? + var text: String? + var to: String? + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "url" { + url = value + } else if queryItem.name == "text" { + text = value + } else if queryItem.name == "to" { + to = value + } + } + } + return .share(url: url, text: text, to: to) } else { for queryItem in queryItems { if let value = queryItem.value { From 289367e28b038c0c0095961c1051b609101a6823 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 11 Apr 2023 23:11:35 +0400 Subject: [PATCH 44/57] Fix forum thread seen list --- .../ChatInterfaceStateContextMenus.swift | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index cdb10a3a387..5235878a23e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -662,7 +662,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState ) } - let readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId)) + let readCounters: Signal + if case let .replyThread(threadMessage) = chatPresentationInterfaceState.chatLocation, threadMessage.isForumPost { + readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ThreadData(id: threadMessage.messageId.peerId, threadId: Int64(threadMessage.messageId.id))) + |> map { threadData -> Bool in + guard let threadData else { + return false + } + return threadData.maxOutgoingReadId >= message.id.id + } + } else { + readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId)) + |> map { readCounters -> Bool in + return readCounters.isOutgoingMessageIndexRead(message.index) + } + } let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest( loadLimits, @@ -679,15 +693,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState context.engine.peers.notificationSoundList() |> take(1), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) ) - |> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, readCounters, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in + |> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, isMessageRead, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in let (limitsConfiguration, appConfig) = limitsAndAppConfig var canEdit = false if !isAction { let message = messages[0] canEdit = canEditMessage(context: context, limitsConfiguration: limitsConfiguration, message: message) } - - let isMessageRead = readCounters.isOutgoingMessageIndexRead(message.index) let translationSettings: TranslationSettings if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { From 4c72c3f75d5f3a18e67e41694145da2677e76610 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 11 Apr 2023 23:11:54 +0400 Subject: [PATCH 45/57] FetchV2 improvements --- Telegram/NotificationService/BUILD | 1 + .../Sources/NotificationService.swift | 23 ++++++-- .../Sources/AnimatedStickerFrameSource.swift | 12 ++-- .../Sources/VideoStickerFrameSource.swift | 16 ++--- .../LottieMeshSwift/Sources/Buffer.swift | 2 +- .../ManagedFile/Sources/ManagedFile.swift | 6 +- submodules/Postbox/Sources/MediaBox.swift | 58 ++++++++++++++++++- submodules/Postbox/Sources/MediaBoxFile.swift | 6 +- .../Sources/MediaBoxFileContextV2Impl.swift | 31 ++++++---- .../Postbox/Sources/MediaBoxFileManager.swift | 4 +- .../Postbox/Sources/MediaBoxFileMap.swift | 2 +- .../Sources/ShareController.swift | 10 +++- .../Sources/AnimatedStickerUtils.swift | 2 +- .../Sources/Network/FetchV2.swift | 4 +- .../Sources/Network/MultipartUpload.swift | 4 +- .../TelegramNotices/Sources/Notices.swift | 25 ++++++++ .../Sources/AnimationCache.swift | 4 +- 17 files changed, 158 insertions(+), 52 deletions(-) diff --git a/Telegram/NotificationService/BUILD b/Telegram/NotificationService/BUILD index edac695af6c..5564908392d 100644 --- a/Telegram/NotificationService/BUILD +++ b/Telegram/NotificationService/BUILD @@ -21,6 +21,7 @@ swift_library( "//submodules/rlottie:RLottieBinding", "//submodules/GZip:GZip", "//submodules/PersistentStringHash:PersistentStringHash", + "//submodules/Utils/RangeSet", ], visibility = [ diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index bb521192d2c..0a067efc1ca 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -15,6 +15,7 @@ import PersistentStringHash import CallKit import AppLockState import NotificationsPresentationData +import RangeSet private let queue = Queue() @@ -1187,7 +1188,7 @@ private final class NotificationServiceHandler { fetchMediaSignal = Signal { subscriber in final class DataValue { var data = Data() - var totalSize: Int64? + var missingRanges = RangeSet(0 ..< Int64.max) } let collectedData = Atomic(value: DataValue()) @@ -1217,12 +1218,22 @@ private final class NotificationServiceHandler { useMainConnection: true ).start(next: { result in switch result { - case let .dataPart(_, data, _, _): + case let .dataPart(offset, data, dataRange, _): var isCompleted = false let _ = collectedData.modify { current in let current = current - current.data.append(data) - if let totalSize = current.totalSize, Int64(current.data.count) >= totalSize { + + let fillRange = Int(offset) ..< (Int(offset) + data.count) + if current.data.count < fillRange.upperBound { + current.data.count = fillRange.upperBound + } + current.data.withUnsafeMutableBytes { buffer -> Void in + let bytes = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self) + data.copyBytes(to: bytes.advanced(by: Int(offset)), from: Int(dataRange.lowerBound) ..< Int(dataRange.upperBound)) + } + current.missingRanges.remove(contentsOf: Int64(fillRange.lowerBound) ..< Int64(fillRange.upperBound)) + + if current.missingRanges.isEmpty { isCompleted = true } return current @@ -1235,8 +1246,8 @@ private final class NotificationServiceHandler { var isCompleted = false let _ = collectedData.modify { current in let current = current - current.totalSize = size - if Int64(current.data.count) >= size { + current.missingRanges.remove(contentsOf: size ..< Int64.max) + if current.missingRanges.isEmpty { isCompleted = true } return current diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerFrameSource.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerFrameSource.swift index 75c01e99176..9d815f99218 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerFrameSource.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerFrameSource.swift @@ -330,7 +330,7 @@ private final class AnimatedStickerDirectFrameSourceCache { return .notFound } - self.file.seek(position: Int64(index * 4 * 2)) + let _ = self.file.seek(position: Int64(index * 4 * 2)) var offset: Int32 = 0 var length: Int32 = 0 if self.file.read(&offset, 4) != 4 { @@ -384,12 +384,12 @@ private final class AnimatedStickerDirectFrameSourceCache { return } - strongSelf.file.seek(position: Int64(index * 4 * 2)) + let _ = strongSelf.file.seek(position: Int64(index * 4 * 2)) var offset = Int32(currentSize) var length = Int32(compressedData.data.count) let _ = strongSelf.file.write(&offset, count: 4) let _ = strongSelf.file.write(&length, count: 4) - strongSelf.file.seek(position: Int64(currentSize)) + let _ = strongSelf.file.seek(position: Int64(currentSize)) compressedData.data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in if let baseAddress = buffer.baseAddress { let _ = strongSelf.file.write(baseAddress, count: Int(length)) @@ -427,12 +427,12 @@ private final class AnimatedStickerDirectFrameSourceCache { return } - strongSelf.file.seek(position: Int64(index * 4 * 2)) + let _ = strongSelf.file.seek(position: Int64(index * 4 * 2)) var offset = Int32(currentSize) var length = Int32(compressedData.count) let _ = strongSelf.file.write(&offset, count: 4) let _ = strongSelf.file.write(&length, count: 4) - strongSelf.file.seek(position: Int64(currentSize)) + let _ = strongSelf.file.seek(position: Int64(currentSize)) compressedData.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in if let baseAddress = buffer.baseAddress { let _ = strongSelf.file.write(baseAddress, count: Int(length)) @@ -502,7 +502,7 @@ private final class AnimatedStickerDirectFrameSourceCache { switch rangeResult { case let .range(range): - self.file.seek(position: Int64(range.lowerBound)) + let _ = self.file.seek(position: Int64(range.lowerBound)) let length = range.upperBound - range.lowerBound let compressedData = self.file.readData(count: length) if compressedData.count != length { diff --git a/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift b/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift index 33318682890..d599a9bb3a5 100644 --- a/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift +++ b/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift @@ -100,7 +100,7 @@ private final class VideoStickerFrameSourceCache { return true } - self.file.seek(position: 0) + let _ = self.file.seek(position: 0) var frameRate: Int32 = 0 if self.file.read(&frameRate, 4) != 4 { return false @@ -113,7 +113,7 @@ private final class VideoStickerFrameSourceCache { } self.frameRate = frameRate - self.file.seek(position: 4) + let _ = self.file.seek(position: 4) var frameCount: Int32 = 0 if self.file.read(&frameCount, 4) != 4 { @@ -144,7 +144,7 @@ private final class VideoStickerFrameSourceCache { return .notFound } - self.file.seek(position: Int64(8 + index * 4 * 2)) + let _ = self.file.seek(position: Int64(8 + index * 4 * 2)) var offset: Int32 = 0 var length: Int32 = 0 if self.file.read(&offset, 4) != 4 { @@ -167,11 +167,11 @@ private final class VideoStickerFrameSourceCache { } func storeFrameRateAndCount(frameRate: Int, frameCount: Int) { - self.file.seek(position: 0) + let _ = self.file.seek(position: 0) var frameRate = Int32(frameRate) let _ = self.file.write(&frameRate, count: 4) - self.file.seek(position: 4) + let _ = self.file.seek(position: 4) var frameCount = Int32(frameCount) let _ = self.file.write(&frameCount, count: 4) } @@ -203,12 +203,12 @@ private final class VideoStickerFrameSourceCache { return } - strongSelf.file.seek(position: Int64(8 + index * 4 * 2)) + let _ = strongSelf.file.seek(position: Int64(8 + index * 4 * 2)) var offset = Int32(currentSize) var length = Int32(compressedData.count) let _ = strongSelf.file.write(&offset, count: 4) let _ = strongSelf.file.write(&length, count: 4) - strongSelf.file.seek(position: Int64(currentSize)) + let _ = strongSelf.file.seek(position: Int64(currentSize)) compressedData.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in if let baseAddress = buffer.baseAddress { let _ = strongSelf.file.write(baseAddress, count: Int(length)) @@ -226,7 +226,7 @@ private final class VideoStickerFrameSourceCache { switch rangeResult { case let .range(range): - self.file.seek(position: Int64(range.lowerBound)) + let _ = self.file.seek(position: Int64(range.lowerBound)) let length = range.upperBound - range.lowerBound let compressedData = self.file.readData(count: length) if compressedData.count != length { diff --git a/submodules/LottieMeshSwift/Sources/Buffer.swift b/submodules/LottieMeshSwift/Sources/Buffer.swift index cff168947f6..3601d4f89a9 100644 --- a/submodules/LottieMeshSwift/Sources/Buffer.swift +++ b/submodules/LottieMeshSwift/Sources/Buffer.swift @@ -75,7 +75,7 @@ public final class MeshWriteBuffer { } public func seek(offset: Int) { - self.file.seek(position: Int64(offset)) + let _ = self.file.seek(position: Int64(offset)) self.offset = offset } } diff --git a/submodules/ManagedFile/Sources/ManagedFile.swift b/submodules/ManagedFile/Sources/ManagedFile.swift index 34ddd4eadcc..66ad61cb81f 100644 --- a/submodules/ManagedFile/Sources/ManagedFile.swift +++ b/submodules/ManagedFile/Sources/ManagedFile.swift @@ -99,12 +99,14 @@ public final class ManagedFile { return result } - public func seek(position: Int64) { + @discardableResult + public func seek(position: Int64) -> Bool { if let queue = self.queue { assert(queue.isCurrent()) } assert(!self.isClosed) - lseek(self.fd, position, SEEK_SET) + let result = lseek(self.fd, position, SEEK_SET) + return result == position } public func truncate(count: Int64) { diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index 8cba2107f6e..99364741a69 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -139,7 +139,7 @@ public final class MediaBox { private let statusQueue = Queue() private let concurrentQueue = Queue.concurrentDefaultQueue() - private let dataQueue = Queue() + private let dataQueue = Queue(name: "MediaBox-Data") private let dataFileManager: MediaBoxFileManager private let cacheQueue = Queue() private let timeBasedCleanup: TimeBasedCleanup @@ -209,6 +209,58 @@ public final class MediaBox { let _ = self.ensureDirectoryCreated //self.updateResourceIndex() + + /*#if DEBUG + self.dataQueue.async { + for _ in 0 ..< 5 { + let tempFile = TempBox.shared.tempFile(fileName: "file") + print("MediaBox test: file \(tempFile.path)") + let queue2 = Queue.concurrentDefaultQueue() + if let fileContext = MediaBoxFileContextV2Impl(queue: self.dataQueue, manager: self.dataFileManager, storageBox: self.storageBox, resourceId: tempFile.path.data(using: .utf8)!, path: tempFile.path + "_complete", partialPath: tempFile.path + "_partial", metaPath: tempFile.path + "_partial" + ".meta") { + let _ = fileContext.fetched( + range: 0 ..< Int64.max, + priority: .default, + fetch: { ranges in + return ranges + |> filter { !$0.isEmpty } + |> take(1) + |> castError(MediaResourceDataFetchError.self) + |> mapToSignal { _ in + return Signal { subscriber in + queue2.async { + subscriber.putNext(.resourceSizeUpdated(524288)) + } + queue2.async { + subscriber.putNext(.resourceSizeUpdated(393216)) + } + queue2.async { + subscriber.putNext(.resourceSizeUpdated(655360)) + } + queue2.async { + subscriber.putNext(.resourceSizeUpdated(169608)) + } + queue2.async { + subscriber.putNext(.dataPart(resourceOffset: 131072, data: Data(repeating: 0xbb, count: 38536), range: 0 ..< 38536, complete: true)) + } + queue2.async { + subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(repeating: 0xaa, count: 131072), range: 0 ..< 131072, complete: false)) + } + + return EmptyDisposable + } + } + }, + error: { _ in + }, + completed: { + assert(try! Data(contentsOf: URL(fileURLWithPath: tempFile.path + "_complete")) == Data(repeating: 0xaa, count: 131072) + Data(repeating: 0xbb, count: 38536)) + let _ = fileContext.addReference() + } + ) + } + } + } + #endif*/ } public func setMaxStoreTimes(general: Int32, shortLived: Int32, gigabytesLimit: Int32) { @@ -688,7 +740,7 @@ public final class MediaBox { let clippedLowerBound = min(completeSize, max(0, range.lowerBound)) let clippedUpperBound = min(completeSize, max(0, range.upperBound)) if clippedLowerBound < clippedUpperBound && (clippedUpperBound - clippedLowerBound) <= 64 * 1024 * 1024 { - file.seek(position: clippedLowerBound) + let _ = file.seek(position: clippedLowerBound) let data = file.readData(count: Int(clippedUpperBound - clippedLowerBound)) subscriber.putNext((data, true)) } else { @@ -725,7 +777,7 @@ public final class MediaBox { subscriber.putNext((Data(), true)) subscriber.putCompletion() } else if clippedUpperBound <= fileSize && (clippedUpperBound - clippedLowerBound) <= 64 * 1024 * 1024 { - file.seek(position: Int64(clippedLowerBound)) + let _ = file.seek(position: Int64(clippedLowerBound)) let resultData = file.readData(count: Int(clippedUpperBound - clippedLowerBound)) subscriber.putNext((resultData, true)) subscriber.putCompletion() diff --git a/submodules/Postbox/Sources/MediaBoxFile.swift b/submodules/Postbox/Sources/MediaBoxFile.swift index ba0d687a3ba..44472bd1ce6 100644 --- a/submodules/Postbox/Sources/MediaBoxFile.swift +++ b/submodules/Postbox/Sources/MediaBoxFile.swift @@ -88,7 +88,7 @@ final class MediaBoxPartialFile { guard let clippedRange = fileMap.contains(range) else { return nil } - fd.seek(position: Int64(clippedRange.lowerBound)) + let _ = fd.seek(position: Int64(clippedRange.lowerBound)) return fd.readData(count: Int(clippedRange.upperBound - clippedRange.lowerBound)) } @@ -227,7 +227,7 @@ final class MediaBoxPartialFile { do { try self.fd.access { fd in - fd.seek(position: offset) + let _ = fd.seek(position: offset) let written = data.withUnsafeBytes { rawBytes -> Int in let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) @@ -330,7 +330,7 @@ final class MediaBoxPartialFile { do { var result: Data? try self.fd.access { fd in - fd.seek(position: Int64(actualRange.lowerBound)) + let _ = fd.seek(position: Int64(actualRange.lowerBound)) var data = Data(count: actualRange.count) let dataCount = data.count let readBytes = data.withUnsafeMutableBytes { rawBytes -> Int in diff --git a/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift b/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift index 560920279ca..fb866c91469 100644 --- a/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift +++ b/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift @@ -421,21 +421,28 @@ final class MediaBoxFileContextV2Impl: MediaBoxFileContext { private func processWrite(resourceOffset: Int64, data: Data, dataRange: Range) { if let destinationFile = self.destinationFile { do { + var success = true try destinationFile.access { fd in - fd.seek(position: resourceOffset) - let written = data.withUnsafeBytes { rawBytes -> Int in - let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) - - return fd.write(bytes.advanced(by: Int(dataRange.lowerBound)), count: dataRange.count) + if fd.seek(position: resourceOffset) { + let written = data.withUnsafeBytes { rawBytes -> Int in + let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) + + return fd.write(bytes.advanced(by: Int(dataRange.lowerBound)), count: dataRange.count) + } + assert(written == dataRange.count) + } else { + success = false } - assert(written == dataRange.count) } - - let range: Range = resourceOffset ..< (resourceOffset + Int64(dataRange.count)) - self.fileMap.fill(range) - self.fileMap.serialize(manager: self.manager, to: self.metaPath) - - self.storageBox.update(id: self.resourceId, size: self.fileMap.sum) + if success { + let range: Range = resourceOffset ..< (resourceOffset + Int64(dataRange.count)) + self.fileMap.fill(range) + self.fileMap.serialize(manager: self.manager, to: self.metaPath) + + self.storageBox.update(id: self.resourceId, size: self.fileMap.sum) + } else { + postboxLog("MediaBoxFileContextV2Impl: error seeking file to \(resourceOffset) at \(self.partialPath)") + } } catch let e { postboxLog("MediaBoxFileContextV2Impl: error writing file at \(self.partialPath): \(e)") } diff --git a/submodules/Postbox/Sources/MediaBoxFileManager.swift b/submodules/Postbox/Sources/MediaBoxFileManager.swift index a98a2ae0a14..bc963b8e4cf 100644 --- a/submodules/Postbox/Sources/MediaBoxFileManager.swift +++ b/submodules/Postbox/Sources/MediaBoxFileManager.swift @@ -32,8 +32,8 @@ final class MediaBoxFileManager { return self.file.readData(count: count) } - func seek(position: Int64) { - self.file.seek(position: position) + func seek(position: Int64) -> Bool { + return self.file.seek(position: position) } } diff --git a/submodules/Postbox/Sources/MediaBoxFileMap.swift b/submodules/Postbox/Sources/MediaBoxFileMap.swift index d353eb61de6..e81872da840 100644 --- a/submodules/Postbox/Sources/MediaBoxFileMap.swift +++ b/submodules/Postbox/Sources/MediaBoxFileMap.swift @@ -203,7 +203,7 @@ final class MediaBoxFileMap { } let _ = try? fileItem.access { file in - file.seek(position: 0) + let _ = file.seek(position: 0) let buffer = WriteBuffer() var magic: UInt32 = 0x7bac1487 buffer.write(&magic, offset: 0, length: 4) diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index d40af9242a5..f3a36a73bef 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -181,9 +181,17 @@ private func collectExternalShareItems(strings: PresentationStrings, dateTimeFor case .progress: return .progress case let .done(data): - if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)), let image = UIImage(data: fileData) { + guard let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else { + return .progress + } + if let image = UIImage(data: fileData) { return .done(.image(image)) } else { + #if DEBUG + if "".isEmpty { + return .done(.file(URL(fileURLWithPath: data.path), "image.bin", "application/octet-stream")) + } + #endif return .progress } } diff --git a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift index 5c52b354377..f28a29dc5fa 100644 --- a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift +++ b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift @@ -387,7 +387,7 @@ public func cacheVideoStickerFrames(path: String, size: CGSize, cacheKey: String } if frameCount > 0 { - file.seek(position: 4) + let _ = file.seek(position: 4) let _ = file.write(&frameCount, count: 4) } diff --git a/submodules/TelegramCore/Sources/Network/FetchV2.swift b/submodules/TelegramCore/Sources/Network/FetchV2.swift index e4a05c99f8f..14d489c6d5c 100644 --- a/submodules/TelegramCore/Sources/Network/FetchV2.swift +++ b/submodules/TelegramCore/Sources/Network/FetchV2.swift @@ -647,8 +647,8 @@ private final class FetchImpl { Logger.shared.log("FetchV2", "\(self.loggingIdentifier): setting known size to \(resultingSize)") self.knownSize = resultingSize } - Logger.shared.log("FetchV2", "\(self.loggingIdentifier): reporting resource size \(fetchRange.lowerBound + actualLength)") - self.onNext(.resourceSizeUpdated(fetchRange.lowerBound + actualLength)) + Logger.shared.log("FetchV2", "\(self.loggingIdentifier): reporting resource size \(resultingSize)") + self.onNext(.resourceSizeUpdated(resultingSize)) } state.completedRanges.formUnion(RangeSet(partRange)) diff --git a/submodules/TelegramCore/Sources/Network/MultipartUpload.swift b/submodules/TelegramCore/Sources/Network/MultipartUpload.swift index 53b79f1e65c..0bff10aa24f 100644 --- a/submodules/TelegramCore/Sources/Network/MultipartUpload.swift +++ b/submodules/TelegramCore/Sources/Network/MultipartUpload.swift @@ -186,7 +186,7 @@ private final class MultipartUploadManager { self.bigTotalParts = nil } else { self.bigParts = false - self.defaultPartSize = 16 * 1024 + self.defaultPartSize = 128 * 1024 self.bigTotalParts = nil } } @@ -317,7 +317,7 @@ private final class MultipartUploadManager { switch resourceData { case let .resourceData(data): if let file = ManagedFile(queue: nil, path: data.path, mode: .read) { - file.seek(position: Int64(partOffset)) + let _ = file.seek(position: Int64(partOffset)) let data = file.readData(count: Int(partSize)) if data.count == partSize { partData = data diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 8995477dff6..aef9b83c446 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -172,6 +172,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case sendWhenOnlineTip = 38 case chatWallpaperLightPreviewTip = 39 case chatWallpaperDarkPreviewTip = 40 + case displayChatListContacts = 41 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -389,6 +390,10 @@ private struct ApplicationSpecificNoticeKeys { static func sendWhenOnlineTip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sendWhenOnlineTip.key) } + + static func displayChatListContacts() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListContacts.key) + } } public struct ApplicationSpecificNotice { @@ -1420,6 +1425,26 @@ public struct ApplicationSpecificNotice { } } + public static func displayChatListContacts(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.displayChatListContacts()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + } + + public static func setDisplayChatListContacts(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.displayChatListContacts(), entry) + } + } + |> ignoreValues + } + public static func reset(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in } diff --git a/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift b/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift index 2d15c251295..29fcb45f514 100644 --- a/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift +++ b/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift @@ -605,10 +605,10 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter { let metadataPosition = file.position() let contentLength = Int(metadataPosition) - contentLengthOffset - 4 - file.seek(position: Int64(contentLengthOffset)) + let _ = file.seek(position: Int64(contentLengthOffset)) file.write(UInt32(contentLength)) - file.seek(position: metadataPosition) + let _ = file.seek(position: metadataPosition) file.write(UInt32(self.frames.count)) for frame in self.frames { file.write(Float32(frame.duration)) From 256439b1382a64f4afdea939aad93fefc6c0f552 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Apr 2023 12:41:07 +0400 Subject: [PATCH 46/57] Chat wallpaper fixes --- .../Themes/CustomWallpaperPicker.swift | 2 +- .../Sources/Themes/WallpaperGalleryItem.swift | 2 +- .../TelegramEngine/Themes/ChatThemes.swift | 23 ++++++----- .../TelegramUI/Sources/ChatThemeScreen.swift | 12 +++++- .../Sources/WallpaperBackgroundNode.swift | 4 +- .../WebUI/Sources/WebAppController.swift | 39 +++++++++++++++---- 6 files changed, 57 insertions(+), 25 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index c5d5b92ee7d..4ce31af8504 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -303,7 +303,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa var intensity: Int32? if let brightness { - intensity = max(1, Int32(brightness * 100.0)) + intensity = max(0, min(100, Int32(brightness * 100.0))) } let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: intensity) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 6de92eefa21..9c1b0cc2253 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -313,7 +313,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } switch entry { case .asset, .contextResult: - return self.sliderNode.value + return 1.0 - self.sliderNode.value default: return nil } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index 406394d9379..8e97221e718 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -125,6 +125,14 @@ func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager return .complete() } return postbox.transaction { transaction -> Signal in + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + if let current = current as? CachedUserData { + return current.withUpdatedWallpaper(wallpaper) + } else { + return current + } + }) + var flags: Int32 = 0 var inputWallpaper: Api.InputWallPaper? var inputSettings: Api.WallPaperSettings? @@ -139,19 +147,10 @@ func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager return .complete() } |> mapToSignal { updates -> Signal in - return postbox.transaction { transaction -> Api.Updates in - transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in - if let current = current as? CachedUserData { - return current.withUpdatedWallpaper(wallpaper) - } else { - return current - } - }) - if applyUpdates { - stateManager.addUpdates(updates) - } - return updates + if applyUpdates { + stateManager.addUpdates(updates) } + return .single(updates) } } |> switchToLatest } diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 1ca1e7719e0..bb157f8e4a2 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -1071,7 +1071,13 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) } - self.cancelButtonNode.theme = presentationData.theme + if let animatingCrossFade = self.animatingCrossFade { + Queue.mainQueue().after(!animatingCrossFade ? ChatThemeScreen.themeCrossfadeDelay * UIView.animationDurationFactor() : 0.0, { + self.cancelButtonNode.setTheme(presentationData.theme, animated: true) + }) + } else { + self.cancelButtonNode.setTheme(presentationData.theme, animated: false) + } let previousIconColors = iconColors(theme: previousTheme) let newIconColors = iconColors(theme: self.presentationData.theme) @@ -1164,6 +1170,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega } } + private var animatingCrossFade: Bool? private func animateCrossfade(animateIcon: Bool) { if animateIcon, let snapshotView = self.animationNode.view.snapshotView(afterScreenUpdates: false) { snapshotView.frame = self.animationNode.frame @@ -1174,6 +1181,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega }) } + self.animatingCrossFade = animateIcon Queue.mainQueue().after(ChatThemeScreen.themeCrossfadeDelay * UIView.animationDurationFactor()) { if let effectView = self.effectNode.view as? UIVisualEffectView { UIView.animate(withDuration: ChatThemeScreen.themeCrossfadeDuration, delay: 0.0, options: .curveLinear) { @@ -1185,6 +1193,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let previousColor = self.contentBackgroundNode.backgroundColor ?? .clear self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor self.contentBackgroundNode.layer.animate(from: previousColor.cgColor, to: (self.contentBackgroundNode.backgroundColor ?? .clear).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: ChatThemeScreen.themeCrossfadeDuration) + + self.animatingCrossFade = nil } if let snapshotView = self.contentContainerNode.view.snapshotView(afterScreenUpdates: false) { diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 7076506512d..957db88bfba 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -917,8 +917,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode default: break } - if let intensity, intensity < 100 { - dimAlpha = 1.0 - max(0.0, min(1.0, Float(intensity) / 100.0)) + if let intensity, intensity > 0 { + dimAlpha = max(0.0, min(1.0, Float(intensity) / 100.0)) } } self.dimLayer.opacity = dimAlpha diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index ca548755936..f6b4df4f4c8 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -38,15 +38,20 @@ public class WebAppCancelButtonNode: ASDisplayNode { public var state: State = .cancel + private var _theme: PresentationTheme public var theme: PresentationTheme { - didSet { - self.setState(self.state, animated: false, force: true) + get { + return self._theme + } + set { + self._theme = newValue + self.setState(self.state, animated: false, animateScale: false, force: true) } } private let strings: PresentationStrings public init(theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme + self._theme = theme self.strings = strings self.buttonNode = HighlightTrackingButtonNode() @@ -55,6 +60,7 @@ public class WebAppCancelButtonNode: ASDisplayNode { self.arrowNode.displaysAsynchronously = false self.labelNode = ImmediateTextNode() + self.labelNode.displaysAsynchronously = false super.init() @@ -82,23 +88,40 @@ public class WebAppCancelButtonNode: ASDisplayNode { self.setState(.cancel, animated: false, force: true) } - public func setState(_ state: State, animated: Bool, force: Bool = false) { + public func setTheme(_ theme: PresentationTheme, animated: Bool) { + self._theme = theme + var animated = animated + if self.animatingStateChange { + animated = false + } + self.setState(self.state, animated: animated, animateScale: false, force: true) + } + + private var animatingStateChange = false + public func setState(_ state: State, animated: Bool, animateScale: Bool = true, force: Bool = false) { guard self.state != state || force else { return } self.state = state if animated, let snapshotView = self.buttonNode.view.snapshotContentTree() { + self.animatingStateChange = true snapshotView.layer.sublayerTransform = self.buttonNode.subnodeTransform self.view.addSubview(snapshotView) - snapshotView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.25, removeOnCompletion: false) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in + let duration: Double = animateScale ? 0.25 : 0.3 + if animateScale { + snapshotView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.25, removeOnCompletion: false) + } + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() + self.animatingStateChange = false }) - self.buttonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - self.buttonNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.25) + if animateScale { + self.buttonNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.25) + } + self.buttonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) } self.arrowNode.isHidden = state == .cancel From 4e72188755db43077c333f069bd6219979d148f2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Apr 2023 19:17:42 +0400 Subject: [PATCH 47/57] Chat wallpaper improvements --- .../TGPhotoEditorController.h | 2 + .../LegacyComponents/TGPhotoEditorUtils.h | 1 + .../LegacyComponents/TGPhotoVideoEditor.h | 2 +- .../Sources/TGCameraCapturedPhoto.m | 3 - .../Sources/TGPhotoEditorController.m | 39 +++- .../Sources/TGPhotoEditorTabController.m | 38 +++- .../Sources/TGPhotoEditorUtils.m | 3 +- .../Sources/TGPhotoToolsController.m | 42 +++-- .../Sources/TGPhotoVideoEditor.m | 63 ++++--- .../Sources/LegacyAttachmentMenu.swift | 23 ++- .../Themes/CustomWallpaperPicker.swift | 74 ++++++++ .../Sources/Themes/WallpaperCropNode.swift | 2 +- .../Themes/WallpaperGalleryController.swift | 55 ++++-- .../Sources/Themes/WallpaperGalleryItem.swift | 161 +++++++++------- .../PendingPeerMediaUploadManager.swift | 175 +++++++----------- 15 files changed, 429 insertions(+), 254 deletions(-) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h index a1ac3a4f8b3..7e480079f4d 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h @@ -68,6 +68,8 @@ typedef enum { @property (nonatomic, strong) UIView *entitiesView; +@property (nonatomic, assign) bool ignoreCropForResult; + - (instancetype)initWithContext:(id)context item:(id)item intent:(TGPhotoEditorControllerIntent)intent adjustments:(id)adjustments caption:(NSAttributedString *)caption screenImage:(UIImage *)screenImage availableTabs:(TGPhotoEditorTab)availableTabs selectedTab:(TGPhotoEditorTab)selectedTab; - (void)dismissEditor; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h index 948ccab2ff3..86a85c00672 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h @@ -46,6 +46,7 @@ CGSize TGPhotoThumbnailSizeForCurrentScreen(); CGSize TGPhotoEditorScreenImageMaxSize(); extern const CGSize TGPhotoEditorResultImageMaxSize; +extern const CGSize TGPhotoEditorResultImageWallpaperMaxSize; #ifdef __cplusplus } diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h index e58613cae3b..6e5c165ac0e 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h @@ -6,6 +6,6 @@ + (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString *)recipientName stickersContext:(id)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed; -+ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed; ++ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item cropRect:(CGRect)cropRect adjustments:(id)adjustments referenceView:(UIView *)referenceView completion:(void (^)(UIImage *, id))completion fullSizeCompletion:(void (^)(UIImage *))fullSizeCompletion beginTransitionOut:(void (^)())beginTransitionOut finishTransitionOut:(void (^)())finishTransitionOut; @end diff --git a/submodules/LegacyComponents/Sources/TGCameraCapturedPhoto.m b/submodules/LegacyComponents/Sources/TGCameraCapturedPhoto.m index 445a7949771..dc6440e3951 100644 --- a/submodules/LegacyComponents/Sources/TGCameraCapturedPhoto.m +++ b/submodules/LegacyComponents/Sources/TGCameraCapturedPhoto.m @@ -81,9 +81,6 @@ - (void)_cleanUp [[NSFileManager defaultManager] removeItemAtPath:[self filePath] error:nil]; } -#define PGTick NSDate *startTime = [NSDate date] -#define PGTock NSLog(@"!=========== %s Time: %f", __func__, -[startTime timeIntervalSinceNow]) - - (void)_saveToDisk:(UIImage *)image { if (image == nil) diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index b9b5dc374d3..7b296a5ff63 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -982,14 +982,23 @@ - (void)createEditedImageWithEditorValues:(id)editorValu bool hasImageAdjustments = editorValues.toolsApplied || saveOnly; bool hasPainting = editorValues.hasPainting; bool hasAnimation = editorValues.paintingData.hasAnimation; + bool ignoreCropForResult = self.ignoreCropForResult; SSignal *(^imageCropSignal)(UIImage *, bool) = ^(UIImage *image, bool resize) { return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - UIImage *paintingImage = !hasImageAdjustments ? editorValues.paintingData.image : nil; - UIImage *croppedImage = TGPhotoEditorCrop(image, paintingImage, photoEditor.cropOrientation, photoEditor.cropRotation, photoEditor.cropRect, photoEditor.cropMirrored, TGPhotoEditorResultImageMaxSize, photoEditor.originalSize, resize); - [subscriber putNext:croppedImage]; + if (ignoreCropForResult) { + if (image.size.width > TGPhotoEditorResultImageWallpaperMaxSize.width || image.size.height > TGPhotoEditorResultImageWallpaperMaxSize.width) { + [subscriber putNext:TGPhotoEditorFitImage(image, TGPhotoEditorResultImageWallpaperMaxSize)]; + } else { + [subscriber putNext:image]; + } + } else { + UIImage *paintingImage = !hasImageAdjustments ? editorValues.paintingData.image : nil; + UIImage *croppedImage = TGPhotoEditorCrop(image, paintingImage, photoEditor.cropOrientation, photoEditor.cropRotation, photoEditor.cropRect, photoEditor.cropMirrored, TGPhotoEditorResultImageMaxSize, photoEditor.originalSize, resize); + [subscriber putNext:croppedImage]; + } [subscriber putCompletion]; return nil; @@ -1045,12 +1054,18 @@ - (void)createEditedImageWithEditorValues:(id)editorValu void (^didFinishRenderingFullSizeImage)(UIImage *) = self.didFinishRenderingFullSizeImage; void (^didFinishEditing)(id, UIImage *, UIImage *, bool , void(^)(void)) = self.didFinishEditing; + TGPhotoEditorControllerIntent intent = _intent; [[[[renderedImageSignal map:^id(UIImage *image) { if (!hasImageAdjustments) { - if (hasPainting && !hasAnimation && didFinishRenderingFullSizeImage != nil) - didFinishRenderingFullSizeImage(image); + if (didFinishRenderingFullSizeImage != nil) { + if (hasPainting && !hasAnimation) { + didFinishRenderingFullSizeImage(image); + } else if (intent == TGPhotoEditorControllerWallpaperIntent) { + didFinishRenderingFullSizeImage(nil); + } + } return image; } @@ -1155,6 +1170,13 @@ - (void)transitionIn _portraitToolbarView.alpha = 1.0f; _landscapeToolbarView.alpha = 1.0f; } completion:nil]; + + if (_intent == TGPhotoEditorControllerWallpaperIntent) { + [UIView animateWithDuration:0.25f delay:0.15 options:UIViewAnimationOptionCurveLinear animations:^ + { + _backgroundView.alpha = 1.0f; + } completion:nil]; + } } - (void)transitionOutSaving:(bool)saving completion:(void (^)(void))completion @@ -1170,6 +1192,13 @@ - (void)transitionOutSaving:(bool)saving completion:(void (^)(void))completion _landscapeToolbarView.alpha = 0.0f; }]; + if (_intent == TGPhotoEditorControllerWallpaperIntent) { + [UIView animateWithDuration:0.1f animations:^ + { + _backgroundView.alpha = 0.0f; + }]; + } + _currentTabController.beginTransitionOut = self.beginTransitionOut; [self setToolbarHidden:false animated:true]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorTabController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorTabController.m index 81a052f13df..9410ea9a1cf 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorTabController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorTabController.m @@ -28,6 +28,9 @@ @interface TGPhotoEditorTabController () UIView *_transitionInReferenceView; UIView *_transitionInParentView; CGRect _transitionTargetFrame; + + UIView *_upperTransitionView; + CGRect _upperTransitionTargetFrame; } @end @@ -114,6 +117,9 @@ - (void)prepareTransitionInWithReferenceView:(UIView *)referenceView referenceFr _dismissing = false; CGRect targetFrame = [self _targetFrameForTransitionInFromFrame:referenceFrame]; + if (self.intent == TGPhotoEditorControllerWallpaperIntent) { + targetFrame = [self.view convertRect:targetFrame toView: parentView]; + } if (_CGRectEqualToRectWithEpsilon(targetFrame, referenceFrame, FLT_EPSILON)) { @@ -157,12 +163,20 @@ - (void)prepareTransitionInWithReferenceView:(UIView *)referenceView referenceFr _transitionView = referenceView; } transitionViewSuperview = parentView; + + if (self.intent == TGPhotoEditorControllerWallpaperIntent) { + _upperTransitionView = [referenceView snapshotViewAfterScreenUpdates:false]; + _upperTransitionView.alpha = 0.0; + _upperTransitionView.frame = [parentView convertRect:referenceFrame toView:self.view]; + _upperTransitionTargetFrame = [self _targetFrameForTransitionInFromFrame:referenceFrame]; + [self.view insertSubview:_upperTransitionView atIndex:0]; + } } _transitionView.hidden = false; _transitionView.frame = referenceFrame; - _transitionTargetFrame = [self _targetFrameForTransitionInFromFrame:referenceFrame]; + _transitionTargetFrame = targetFrame; [transitionViewSuperview addSubview:_transitionView]; } @@ -174,6 +188,9 @@ - (void)animateTransitionIn _transitionInProgress = true; CGAffineTransform initialTransform = _transitionView.transform; + [UIView animateWithDuration:0.25 animations:^{ + _upperTransitionView.alpha = 1.0; + }]; [UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionLayoutSubviews animations:^ { if (_animateScale) { @@ -182,6 +199,7 @@ - (void)animateTransitionIn _transitionView.transform = CGAffineTransformScale(initialTransform, scale, scale); } else { _transitionView.frame = _transitionTargetFrame; + _upperTransitionView.frame = _upperTransitionTargetFrame; } } completion:^(BOOL finished) { _transitionInProgress = false; @@ -201,6 +219,8 @@ - (void)animateTransitionIn } [self _finishedTransitionInWithView:transitionView]; + [_upperTransitionView removeFromSuperview]; + _upperTransitionView = nil; }]; } @@ -275,7 +295,11 @@ - (void)transitionOutSaving:(bool)saving completion:(void (^)(void))completion if (saving) { - [self _animatePreviewViewTransitionOutToFrame:CGRectNull saving:saving parentView:parentView completion:^ + CGRect targetFrame = CGRectNull; + if (self.intent == TGPhotoEditorControllerWallpaperIntent) { + targetFrame = referenceFrame; + } + [self _animatePreviewViewTransitionOutToFrame:targetFrame saving:saving parentView:parentView completion:^ { if (completion != nil) completion(); @@ -316,6 +340,9 @@ - (void)transitionOutSaving:(bool)saving completion:(void (^)(void))completion orientation = UIInterfaceOrientationPortrait; CGRect sourceFrame = [self transitionOutSourceFrameForReferenceFrame:referenceView.frame orientation:orientation]; + if (self.intent == TGPhotoEditorControllerWallpaperIntent) { + sourceFrame = [self.view convertRect:sourceFrame toView: parentView]; + } CGRect targetFrame = referenceFrame; toTransitionView.frame = sourceFrame; @@ -334,7 +361,12 @@ - (void)transitionOutSaving:(bool)saving completion:(void (^)(void))completion }; [animations addObject:@1]; - [self _animatePreviewViewTransitionOutToFrame:targetFrame saving:saving parentView:nil completion:^ + + CGRect previewTargetFrame = targetFrame; + if (self.intent == TGPhotoEditorControllerWallpaperIntent) { + previewTargetFrame = [referenceView convertRect:referenceView.bounds toView:self.view]; + } + [self _animatePreviewViewTransitionOutToFrame:previewTargetFrame saving:saving parentView:nil completion:^ { onAnimationCompletion(@1); }]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m b/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m index 6212c9d14e4..949bb0b5108 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m @@ -6,7 +6,8 @@ #import #import -const CGSize TGPhotoEditorResultImageMaxSize = { 2560, 2560 }; +const CGSize TGPhotoEditorResultImageMaxSize = { 1280, 1280 }; +const CGSize TGPhotoEditorResultImageWallpaperMaxSize = { 2048, 2048 }; const CGSize TGPhotoEditorScreenImageHardLimitSize = { 1280, 1280 }; const CGSize TGPhotoEditorScreenImageHardLimitLegacySize = { 750, 750 }; diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolsController.m b/submodules/LegacyComponents/Sources/TGPhotoToolsController.m index abede25b269..1dfec01a9e2 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoToolsController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoToolsController.m @@ -454,24 +454,40 @@ - (void)_animatePreviewViewTransitionOutToFrame:(CGRect)targetFrame saving:(bool UIView *snapshotView = nil; POPSpringAnimation *snapshotAnimation = nil; - if (saving && CGRectIsNull(targetFrame) && parentView != nil) + if (saving && parentView != nil) { - snapshotView = [previewView snapshotViewAfterScreenUpdates:false]; - snapshotView.frame = previewView.frame; - - CGSize fittedSize = TGScaleToSize(previewView.frame.size, self.view.frame.size); - targetFrame = CGRectMake((self.view.frame.size.width - fittedSize.width) / 2, (self.view.frame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); - - [parentView addSubview:snapshotView]; - - snapshotAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; - snapshotAnimation.fromValue = [NSValue valueWithCGRect:snapshotView.frame]; - snapshotAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; + if (CGRectIsNull(targetFrame)) { + snapshotView = [previewView snapshotViewAfterScreenUpdates:false]; + snapshotView.frame = previewView.frame; + + CGSize fittedSize = TGScaleToSize(previewView.frame.size, self.view.frame.size); + targetFrame = CGRectMake((self.view.frame.size.width - fittedSize.width) / 2, (self.view.frame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); + + [parentView addSubview:snapshotView]; + + snapshotAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; + snapshotAnimation.fromValue = [NSValue valueWithCGRect:snapshotView.frame]; + snapshotAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; + } else if (self.intent == TGPhotoEditorControllerWallpaperIntent) { + snapshotView = [previewView snapshotViewAfterScreenUpdates:false]; + snapshotView.frame = [self.view convertRect:previewView.frame toView:parentView]; + + [parentView addSubview:snapshotView]; + + snapshotAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; + snapshotAnimation.fromValue = [NSValue valueWithCGRect:snapshotView.frame]; + snapshotAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; + } + } + + CGRect previewTargetFrame = targetFrame; + if (self.intent == TGPhotoEditorControllerWallpaperIntent && saving) { + previewTargetFrame = [parentView convertRect:targetFrame toView:self.view]; } POPSpringAnimation *previewAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; previewAnimation.fromValue = [NSValue valueWithCGRect:previewView.frame]; - previewAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; + previewAnimation.toValue = [NSValue valueWithCGRect:previewTargetFrame]; POPSpringAnimation *previewAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha]; previewAlphaAnimation.fromValue = @(previewView.alpha); diff --git a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m index 60b958e2267..537beec6c3d 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m +++ b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m @@ -283,7 +283,7 @@ + (void)presentWithContext:(id)context controller:(TGVi } } -+ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed ++ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item cropRect:(CGRect)cropRect adjustments:(id)adjustments referenceView:(UIView *)referenceView completion:(void (^)(UIImage *, id))completion fullSizeCompletion:(void (^)(UIImage *))fullSizeCompletion beginTransitionOut:(void (^)())beginTransitionOut finishTransitionOut:(void (^)())finishTransitionOut; { id windowManager = [context makeOverlayWindowManager]; @@ -291,40 +291,67 @@ + (void)presentEditorWithContext:(id)context controller UIImage *thumbnailImage; - TGPhotoEditorController *editorController = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:item intent:TGPhotoEditorControllerWallpaperIntent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:TGPhotoEditorToolsTab selectedTab:TGPhotoEditorToolsTab]; + NSDictionary *toolValues; + if (adjustments != nil) { + toolValues = adjustments.toolValues; + } else { + toolValues = @{}; + } + PGPhotoEditorValues *editorValues = [PGPhotoEditorValues editorValuesWithOriginalSize:item.originalSize cropRect:cropRect cropRotation:0.0f cropOrientation:UIImageOrientationUp cropLockedAspectRatio:0.0 cropMirrored:false toolValues:toolValues paintingData:nil sendAsGif:false]; + + TGPhotoEditorController *editorController = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:item intent:TGPhotoEditorControllerWallpaperIntent adjustments:editorValues caption:nil screenImage:thumbnailImage availableTabs:TGPhotoEditorToolsTab selectedTab:TGPhotoEditorToolsTab]; editorController.editingContext = editingContext; editorController.dontHideStatusBar = true; + editorController.ignoreCropForResult = true; - editorController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, __unused UIView **parentView) + CGRect fromRect = referenceView.frame;// [referenceView convertRect:referenceView.bounds toView:nil]; + editorController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView) { *referenceFrame = fromRect; - - UIImageView *imageView = [[UIImageView alloc] initWithFrame:fromRect]; + *parentView = referenceView.superview; + //UIImageView *imageView = [[UIImageView alloc] initWithFrame:fromRect]; //imageView.image = image; - return imageView; + return referenceView; }; - editorController.beginTransitionOut = ^UIView *(CGRect *referenceFrame, __unused UIView **parentView) + editorController.beginTransitionOut = ^UIView *(CGRect *referenceFrame, UIView **parentView) { CGRect startFrame = CGRectZero; if (referenceFrame != NULL) { startFrame = *referenceFrame; *referenceFrame = fromRect; + *parentView = referenceView.superview; } - //[strongSelf transitionBackFromResultControllerWithReferenceFrame:startFrame]; + if (beginTransitionOut) { + beginTransitionOut(); + } - return nil; //strongSelf->_previewView; + return referenceView; + }; + + __weak TGPhotoEditorController *weakController = editorController; + editorController.finishedTransitionOut = ^(bool saved) { + TGPhotoEditorController *strongGalleryController = weakController; + if (strongGalleryController != nil && strongGalleryController.overlayWindow == nil) + { + TGNavigationController *navigationController = (TGNavigationController *)strongGalleryController.navigationController; + TGOverlayControllerWindow *window = (TGOverlayControllerWindow *)navigationController.view.window; + if ([window isKindOfClass:[TGOverlayControllerWindow class]]) + [window dismiss]; + } + if (finishTransitionOut) { + finishTransitionOut(); + } }; editorController.didFinishRenderingFullSizeImage = ^(UIImage *resultImage) { - + fullSizeCompletion(resultImage); }; - __weak TGPhotoEditorController *weakController = editorController; editorController.didFinishEditing = ^(id adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges, void(^commit)(void)) { if (!hasChanges) @@ -334,6 +361,8 @@ + (void)presentEditorWithContext:(id)context controller if (strongController == nil) return; + completion(resultImage, adjustments); + }; editorController.requestThumbnailImage = ^(id editableItem) { @@ -347,17 +376,7 @@ + (void)presentEditorWithContext:(id)context controller editorController.requestOriginalFullSizeImage = ^(id editableItem, NSTimeInterval position) { - if (editableItem.isVideo) { - if ([editableItem isKindOfClass:[TGMediaAsset class]]) { - return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem allowNetworkAccess:true]; - } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { - return ((TGCameraCapturedVideo *)editableItem).avAsset; - } else { - return [editableItem originalImageSignal:position]; - } - } else { - return [editableItem originalImageSignal:position]; - } + return [editableItem originalImageSignal:position]; }; TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:editorController]; diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index 2adce3e163d..9c5a507cd7d 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -63,9 +63,8 @@ public enum LegacyMediaEditorMode { case adjustments } -public func legacyWallpaperEditor(context: AccountContext, image: UIImage, fromRect: CGRect, mainSnapshot: UIView, snapshots: [UIView], transitionCompletion: (() -> Void)?, completion: @escaping (UIImage, TGMediaEditAdjustments?) -> Void, present: @escaping (ViewController, Any?) -> Void) { - let item = TGCameraCapturedPhoto(existing: image) - + +public func legacyWallpaperEditor(context: AccountContext, item: TGMediaEditableItem, cropRect: CGRect, adjustments: TGMediaEditAdjustments?, referenceView: UIView, beginTransitionOut: (() -> Void)?, finishTransitionOut: (() -> Void)?, completion: @escaping (UIImage?, TGMediaEditAdjustments?) -> Void, fullSizeCompletion: @escaping (UIImage?) -> Void, present: @escaping (ViewController, Any?) -> Void) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil) legacyController.blocksBackgroundWhenInOverlay = true @@ -85,17 +84,17 @@ public func legacyWallpaperEditor(context: AccountContext, image: UIImage, fromR present(legacyController, nil) - TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, withItem: item, from: fromRect, mainSnapshot: mainSnapshot, snapshots: snapshots as [Any], completion: { item, editingContext in - let adjustments = editingContext?.adjustments(for: item) - if let imageSignal = editingContext?.fullSizeImageUrl(for: item) { - imageSignal.start(next: { value in - if let value = value as? NSURL, let data = try? Data(contentsOf: value as URL), let image = UIImage(data: data) { - completion(image, adjustments) - } - }, error: { _ in }, completed: {}) + TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, with: item, cropRect: cropRect, adjustments: adjustments, referenceView: referenceView, completion: { image, adjustments in + completion(image, adjustments) + }, fullSizeCompletion: { image in + Queue.mainQueue().async { + fullSizeCompletion(image) } - }, dismissed: { [weak legacyController] in + }, beginTransitionOut: { + beginTransitionOut?() + }, finishTransitionOut: { [weak legacyController] in legacyController?.dismiss() + finishTransitionOut?() }) } diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index 4ce31af8504..d938dd03587 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -318,3 +318,77 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa return croppedImage }).start() } + +class LegacyWallpaperItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem { + var isVideo: Bool { + return false + } + + var uniqueIdentifier: String! { + return self.asset.localIdentifier + } + + let asset: PHAsset + let screenImage: UIImage + private(set) var thumbnailResource: TelegramMediaResource? + private(set) var imageResource: TelegramMediaResource? + let dimensions: CGSize + + + init(asset: PHAsset, screenImage: UIImage, dimensions: CGSize) { + self.asset = asset + self.screenImage = screenImage + self.dimensions = dimensions + } + + var originalSize: CGSize { + return self.dimensions + } + + func thumbnailImageSignal() -> SSignal! { + return SSignal.complete() +// return SSignal(generator: { subscriber -> SDisposable? in +// let disposable = self.thumbnailImage.start(next: { image in +// subscriber.putNext(image) +// subscriber.putCompletion() +// }) +// +// return SBlockDisposable(block: { +// disposable.dispose() +// }) +// }) + } + + func screenImageSignal(_ position: TimeInterval) -> SSignal! { + return SSignal.single(self.screenImage) + } + + var originalImage: Signal { + return fetchPhotoLibraryImage(localIdentifier: self.asset.localIdentifier, thumbnail: false) + |> filter { value in + return !(value?.1 ?? true) + } + |> mapToSignal { result -> Signal in + if let result = result { + return .single(result.0) + } else { + return .complete() + } + } + } + + func originalImageSignal(_ position: TimeInterval) -> SSignal! { + return SSignal(generator: { subscriber -> SDisposable? in + let disposable = self.originalImage.start(next: { image in + subscriber.putNext(image) + if !image.degraded() { + subscriber.putCompletion() + } + }) + + return SBlockDisposable(block: { + disposable.dispose() + }) + }) + } +} diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperCropNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperCropNode.swift index d87c4232548..00dbe5b327f 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperCropNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperCropNode.swift @@ -4,7 +4,7 @@ import Display import AsyncDisplayKit final class WallpaperCropNode: ASDisplayNode, UIScrollViewDelegate { - private let scrollNode: ASScrollNode + let scrollNode: ASScrollNode private var ignoreZoom = false private var ignoreZoomTransition: ContainedViewLayoutTransition? diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift index cfd9702754e..5352a07f7ad 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift @@ -167,10 +167,16 @@ private func updatedFileWallpaper(id: Int64? = nil, accessHash: Int64? = nil, sl } class WallpaperGalleryInteraction { - let editMedia: (UIImage, CGRect, UIView, [UIView], @escaping (UIImage, TGMediaEditAdjustments?) -> Void) -> Void + let editMedia: (PHAsset, UIImage, CGRect, TGMediaEditAdjustments?, UIView, @escaping (UIImage?, TGMediaEditAdjustments?) -> Void, @escaping (UIImage?) -> Void) -> Void + let beginTransitionToEditor: () -> Void + let beginTransitionFromEditor: () -> Void + let finishTransitionFromEditor: () -> Void - init(editMedia: @escaping (UIImage, CGRect, UIView, [UIView], @escaping (UIImage, TGMediaEditAdjustments?) -> Void) -> Void) { + init(editMedia: @escaping (PHAsset, UIImage, CGRect, TGMediaEditAdjustments?, UIView, @escaping (UIImage?, TGMediaEditAdjustments?) -> Void, @escaping (UIImage?) -> Void) -> Void, beginTransitionToEditor: @escaping () -> Void, beginTransitionFromEditor: @escaping () -> Void, finishTransitionFromEditor: @escaping () -> Void) { self.editMedia = editMedia + self.beginTransitionToEditor = beginTransitionToEditor + self.beginTransitionFromEditor = beginTransitionFromEditor + self.finishTransitionFromEditor = finishTransitionFromEditor } } @@ -242,25 +248,50 @@ public class WallpaperGalleryController: ViewController { //self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - self.interaction = WallpaperGalleryInteraction(editMedia: { [weak self] image, fromRect, mainSnapshot, snapshots, apply in + self.interaction = WallpaperGalleryInteraction(editMedia: { [weak self] asset, image, cropRect, adjustments, referenceView, apply, fullSizeApply in guard let self else { return } - var snapshots = snapshots - if let toolbarNode = self.toolbarNode, let snapshotView = toolbarNode.view.snapshotContentTree() { - snapshotView.frame = toolbarNode.view.convert(toolbarNode.view.bounds, to: nil) - snapshots.append(snapshotView) - } - - legacyWallpaperEditor(context: context, image: image, fromRect: fromRect, mainSnapshot: mainSnapshot, snapshots: snapshots, transitionCompletion: { - + let item = LegacyWallpaperItem(asset: asset, screenImage: image, dimensions: CGSize(width: asset.pixelWidth, height: asset.pixelHeight)) + legacyWallpaperEditor(context: context, item: item, cropRect: cropRect, adjustments: adjustments, referenceView: referenceView, beginTransitionOut: { [weak self] in + self?.interaction?.beginTransitionFromEditor() + }, finishTransitionOut: { [weak self] in + self?.interaction?.finishTransitionFromEditor() }, completion: { image, adjustments in apply(image, adjustments) + }, fullSizeCompletion: { image in + fullSizeApply(image) }, present: { [weak self] c, a in if let self { self.present(c, in: .window(.root)) } }) + }, beginTransitionToEditor: { [weak self] in + guard let self else { + return + } + let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) + if let toolbarNode = self.toolbarNode { + transition.updateAlpha(node: toolbarNode, alpha: 0.0) + } + }, beginTransitionFromEditor: { [weak self] in + guard let self else { + return + } + let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) + if let toolbarNode = self.toolbarNode { + transition.updateAlpha(node: toolbarNode, alpha: 1.0) + } + if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { + centralItemNode.beginTransitionFromEditor() + } + }, finishTransitionFromEditor: { [weak self] in + guard let self else { + return + } + if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { + centralItemNode.finishTransitionFromEditor() + } }) var entries: [WallpaperGalleryEntry] = [] @@ -491,7 +522,7 @@ public class WallpaperGalleryController: ViewController { let entry = strongSelf.entries[centralItemNode.index] if case .peer = strongSelf.mode { - strongSelf.apply?(entry, options, centralItemNode.editedImage, centralItemNode.cropRect, centralItemNode.brightness) + strongSelf.apply?(entry, options, centralItemNode.editedFullSizeImage, centralItemNode.editedCropRect, centralItemNode.brightness) return } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 9c1b0cc2253..3ceb39712ce 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -99,6 +99,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let wrapperNode: ASDisplayNode let imageNode: TransformImageNode + private let temporaryImageNode: ASImageNode let nativeNode: WallpaperBackgroundNode let brightnessNode: ASDisplayNode private let statusNode: RadialStatusNode @@ -110,6 +111,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private let dayNightButtonNode: WallpaperNavigationButtonNode private let editButtonNode: WallpaperNavigationButtonNode + private let buttonsContainerNode: SparseNode private let blurButtonNode: WallpaperOptionButtonNode private let motionButtonNode: WallpaperOptionButtonNode private let patternButtonNode: WallpaperOptionButtonNode @@ -159,6 +161,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.wrapperNode = ASDisplayNode() self.imageNode = TransformImageNode() self.imageNode.contentAnimations = .subsequentUpdates + self.temporaryImageNode = ASImageNode() + self.temporaryImageNode.isUserInteractionEnabled = false self.nativeNode = createWallpaperBackgroundNode(context: context, forChatDisplay: false) self.cropNode = WallpaperCropNode() self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) @@ -173,6 +177,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) self.messagesContainerNode.isUserInteractionEnabled = false + self.buttonsContainerNode = SparseNode() self.blurButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Blurred, value: .check(false)) self.blurButtonNode.setEnabled(false) self.motionButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Motion, value: .check(false)) @@ -248,20 +253,22 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.imageNode.clipsToBounds = true self.addSubnode(self.wrapperNode) + self.addSubnode(self.temporaryImageNode) //self.addSubnode(self.statusNode) self.addSubnode(self.serviceBackgroundNode) self.addSubnode(self.messagesContainerNode) - - self.addSubnode(self.blurButtonNode) - self.addSubnode(self.motionButtonNode) - self.addSubnode(self.patternButtonNode) - self.addSubnode(self.colorsButtonNode) - self.addSubnode(self.playButtonNode) - self.addSubnode(self.sliderNode) - self.addSubnode(self.cancelButtonNode) - self.addSubnode(self.shareButtonNode) - self.addSubnode(self.dayNightButtonNode) - self.addSubnode(self.editButtonNode) + self.addSubnode(self.buttonsContainerNode) + + self.buttonsContainerNode.addSubnode(self.blurButtonNode) + self.buttonsContainerNode.addSubnode(self.motionButtonNode) + self.buttonsContainerNode.addSubnode(self.patternButtonNode) + self.buttonsContainerNode.addSubnode(self.colorsButtonNode) + self.buttonsContainerNode.addSubnode(self.playButtonNode) + self.buttonsContainerNode.addSubnode(self.sliderNode) + self.buttonsContainerNode.addSubnode(self.cancelButtonNode) + self.buttonsContainerNode.addSubnode(self.shareButtonNode) + self.buttonsContainerNode.addSubnode(self.dayNightButtonNode) + self.buttonsContainerNode.addSubnode(self.editButtonNode) self.imageNode.addSubnode(self.brightnessNode) @@ -295,18 +302,24 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } switch entry { case .asset, .contextResult: - if let editedImage = self.editedImage { - let scale = editedImage.size.height / self.cropNode.cropRect.height - let cropRect = self.cropNode.cropRect - return CGRect(origin: CGPoint(x: cropRect.minX * scale, y: cropRect.minY * scale), size: CGSize(width: cropRect.width * scale, height: cropRect.height * scale)) - } else { - return self.cropNode.cropRect - } + return self.cropNode.cropRect default: return nil } } + var editedCropRect: CGRect? { + guard let cropRect = self.cropRect, let contentSize = self.contentSize else { + return nil + } + if let editedFullSizeImage = self.editedFullSizeImage { + let scale = editedFullSizeImage.size.height / contentSize.height + return CGRect(origin: CGPoint(x: cropRect.minX * scale, y: cropRect.minY * scale), size: CGSize(width: cropRect.width * scale, height: cropRect.height * scale)) + } else { + return cropRect + } + } + var brightness: CGFloat? { guard let entry = self.entry else { return nil @@ -467,67 +480,62 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } @objc private func editPressed() { - guard let image = self.imageNode.image else { + guard let image = self.imageNode.image, case let .asset(asset) = self.entry else { return } - let originalImage = self.originalImage ?? image - - var nodesToSnapshot = [ - self.cancelButtonNode, - self.editButtonNode, - self.blurButtonNode, - self.motionButtonNode, - self.serviceBackgroundNode - ] - - if let messageNodes = self.messageNodes { - for node in messageNodes { - nodesToSnapshot.append(node) - } + guard let cropRect = self.cropRect else { + return } - - var snapshots: [UIView] = [] - for node in nodesToSnapshot { - if let snapshotView = node.view.snapshotContentTree() { - snapshotView.frame = node.view.convert(node.view.bounds, to: nil) - snapshots.append(snapshotView) + self.interaction?.editMedia(asset, originalImage, cropRect, self.currentAdjustments, self.cropNode.view, { [weak self] result, adjustments in + guard let self else { + return } - } - - if let snapshotView = self.dayNightButtonNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = self.dayNightButtonNode.view.convert(self.dayNightButtonNode.view.bounds, to: nil) - snapshots.append(snapshotView) - } - - let mainSnapshotView: UIView - if let snapshotView = self.imageNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = self.imageNode.view.convert(self.imageNode.view.bounds, to: nil) - mainSnapshotView = snapshotView - } else { - mainSnapshotView = UIView() - } - - let fromRect = self.imageNode.view.convert(self.imageNode.bounds, to: nil) - self.interaction?.editMedia(originalImage, fromRect, mainSnapshotView, snapshots, { [weak self] result, adjustments in - self?.originalImage = originalImage - self?.editedImage = result - self?.currentAdjustments = adjustments + self.originalImage = originalImage + self.editedImage = result + self.currentAdjustments = adjustments - self?.imageNode.setSignal(.single({ arguments in + self.imageNode.setSignal(.single({ arguments in let context = DrawingContext(size: arguments.drawingSize, opaque: false) context?.withFlippedContext({ context in - if let cgImage = result.cgImage { + let image = result ?? originalImage + if let cgImage = image.cgImage { context.draw(cgImage, in: CGRect(origin: .zero, size: arguments.drawingSize)) } }) return context })) + + self.imageNode.imageUpdated = { [weak self] _ in + guard let self else { + return + } + self.temporaryImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.2, removeOnCompletion: false, completion: { [weak self] _ in + self?.temporaryImageNode.image = nil + self?.temporaryImageNode.layer.removeAllAnimations() + self?.imageNode.imageUpdated = nil + }) + } + }, { [weak self] image in + guard let self else { + return + } + self.editedFullSizeImage = image + + self.temporaryImageNode.frame = self.imageNode.view.convert(self.imageNode.bounds, to: self.view) + self.temporaryImageNode.image = image ?? originalImage + + if self.cropNode.isHidden { + self.temporaryImageNode.alpha = 0.0 + } }) + + self.beginTransitionToEditor() } private var originalImage: UIImage? public private(set) var editedImage: UIImage? + public private(set) var editedFullSizeImage: UIImage? private var currentAdjustments: TGMediaEditAdjustments? private func animateIntensityChange(delay: Double) { @@ -557,6 +565,29 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } + func beginTransitionToEditor() { + self.cropNode.isHidden = true + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) + transition.updateAlpha(node: self.messagesContainerNode, alpha: 0.0) + transition.updateAlpha(node: self.buttonsContainerNode, alpha: 0.0) + transition.updateAlpha(node: self.serviceBackgroundNode, alpha: 0.0) + + self.interaction?.beginTransitionToEditor() + } + + func beginTransitionFromEditor() { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) + transition.updateAlpha(node: self.messagesContainerNode, alpha: 1.0) + transition.updateAlpha(node: self.buttonsContainerNode, alpha: 1.0) + transition.updateAlpha(node: self.serviceBackgroundNode, alpha: 1.0) + } + + func finishTransitionFromEditor() { + self.cropNode.isHidden = false + self.temporaryImageNode.alpha = 1.0 + } + @objc private func cancelPressed() { self.dismiss() } @@ -579,11 +610,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { var showPreviewTooltip = false if self.entry != entry || self.arguments.colorPreview != previousArguments.colorPreview { - let previousEntry = self.entry self.entry = entry - if previousEntry != entry { - self.preparePatternEditing() - } self.colorsButtonNode.colors = self.calculateGradientColors() ?? defaultBuiltinWallpaperGradientColors @@ -1204,9 +1231,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } - private func preparePatternEditing() { - } - func setMotionEnabled(_ enabled: Bool, animated: Bool) { if enabled { let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis) @@ -1606,6 +1630,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.wrapperNode.bounds = CGRect(origin: CGPoint(), size: layout.size) self.updateWrapperLayout(layout: layout, offset: offset, transition: transition) self.messagesContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size) + self.buttonsContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size) if self.cropNode.supernode == nil { self.imageNode.frame = self.wrapperNode.bounds diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift index dabe3847dfd..2e6468064ee 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift @@ -13,23 +13,17 @@ public final class PeerMediaUploadingItem: Equatable { 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) { + init(content: Content, messageId: EngineMessage.Id?, progress: Float) { self.content = content self.messageId = messageId - self.previousState = previousState self.progress = progress } @@ -40,9 +34,6 @@ public final class PeerMediaUploadingItem: Equatable { if lhs.messageId != rhs.messageId { return false } - if lhs.previousState != rhs.previousState { - return false - } if lhs.progress != rhs.progress { return false } @@ -50,15 +41,11 @@ public final class PeerMediaUploadingItem: Equatable { } func withMessageId(_ messageId: EngineMessage.Id) -> PeerMediaUploadingItem { - return PeerMediaUploadingItem(content: self.content, messageId: messageId, previousState: self.previousState, progress: self.progress) + return PeerMediaUploadingItem(content: self.content, messageId: messageId, 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) + return PeerMediaUploadingItem(content: self.content, messageId: self.messageId, progress: progress) } } @@ -129,38 +116,6 @@ private func generatePeerMediaMessage(network: Network, accountPeerId: EnginePee 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() @@ -225,98 +180,93 @@ private final class PendingPeerMediaUploadManagerImpl { let accountPeerId = self.accountPeerId let queue = self.queue - let context = PendingPeerMediaUploadContext(value: PeerMediaUploadingItem(content: content, messageId: nil, previousState: nil, progress: 0.0)) + let context = PendingPeerMediaUploadContext(value: PeerMediaUploadingItem(content: content, messageId: nil, progress: 0.0)) self.contexts[peerId] = context context.disposable.set( - (self.postbox.transaction({ transaction -> (EngineMessage.Id, PeerMediaUploadingItem.PreviousState?)? in + (self.postbox.transaction({ transaction -> EngineMessage.Id? 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) + return messageId }) - |> deliverOn(queue)).start(next: { [weak self, weak context] messageIdAndPreviousState in + |> deliverOn(queue)).start(next: { [weak self, weak context] messageId in guard let strongSelf = self, let initialContext = context else { return } if let context = strongSelf.contexts[peerId], context === initialContext { - guard let (messageId, previousState) = messageIdAndPreviousState else { + guard let messageId = messageId else { strongSelf.contexts.removeValue(forKey: peerId) context.disposable.dispose() strongSelf.updateValues() return } - context.value = context.value.withMessageId(messageId).withPreviousState(previousState) + context.value = context.value.withMessageId(messageId) 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 { + 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 } - 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() - } - }) + context.disposable.set( + (applyUpdateMessage( + postbox: postbox, + stateManager: stateManager, + message: message, + cacheReferenceKey: nil, + result: result, + accountPeerId: accountPeerId ) - } - }) - ) - strongSelf.updateValues() - case let .progress(progress): - context.value = context.value.withProgress(progress) - strongSelf.updateValues() - } + |> 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() - } + 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() } })) } @@ -330,7 +280,6 @@ private final class PendingPeerMediaUploadManagerImpl { 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 { From 61cc0b5ed19bbbe58a0d275d50ea6060073ca845 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Apr 2023 19:30:17 +0400 Subject: [PATCH 48/57] Chat wallpaper improvements --- .../Sources/Themes/WallpaperGalleryItem.swift | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 3ceb39712ce..dd37931e634 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -505,17 +505,12 @@ final class WallpaperGalleryItemNode: GalleryItemNode { }) return context })) - - self.imageNode.imageUpdated = { [weak self] _ in - guard let self else { - return - } - self.temporaryImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.2, removeOnCompletion: false, completion: { [weak self] _ in - self?.temporaryImageNode.image = nil - self?.temporaryImageNode.layer.removeAllAnimations() - self?.imageNode.imageUpdated = nil - }) - } + + self.temporaryImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.2, removeOnCompletion: false, completion: { [weak self] _ in + self?.temporaryImageNode.image = nil + self?.temporaryImageNode.layer.removeAllAnimations() + self?.imageNode.imageUpdated = nil + }) }, { [weak self] image in guard let self else { return From 32251e1ef2d2852be730292229767385bed0b2d7 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Apr 2023 19:37:39 +0400 Subject: [PATCH 49/57] Chat wallpaper fixes --- submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index dd37931e634..a05ca062e67 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -509,7 +509,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.temporaryImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.2, removeOnCompletion: false, completion: { [weak self] _ in self?.temporaryImageNode.image = nil self?.temporaryImageNode.layer.removeAllAnimations() - self?.imageNode.imageUpdated = nil }) }, { [weak self] image in guard let self else { From 8fd43ef1d67f3c98d7f307fe0f1970518e314368 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 12 Apr 2023 20:48:28 +0400 Subject: [PATCH 50/57] Update build system --- build-system/Make/ProjectGeneration.py | 2 ++ build-system/bazel-rules/rules_xcodeproj | 2 +- submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh | 2 +- third-party/yasm/BUILD | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build-system/Make/ProjectGeneration.py b/build-system/Make/ProjectGeneration.py index 046ee4d9bdc..05a326f4418 100644 --- a/build-system/Make/ProjectGeneration.py +++ b/build-system/Make/ProjectGeneration.py @@ -24,6 +24,7 @@ def generate_xcodeproj(build_environment: BuildEnvironment, disable_extensions, bazel_generate_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)] if disable_extensions: bazel_generate_arguments += ['--//{}:disableExtensions'.format(app_target)] + bazel_generate_arguments += ['--//{}:disableStripping'.format('Telegram')] project_bazel_arguments = [] for argument in bazel_app_arguments: @@ -31,6 +32,7 @@ def generate_xcodeproj(build_environment: BuildEnvironment, disable_extensions, project_bazel_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)] if disable_extensions: project_bazel_arguments += ['--//{}:disableExtensions'.format(app_target)] + project_bazel_arguments += ['--//{}:disableStripping'.format('Telegram')] xcodeproj_bazelrc = os.path.join(build_environment.base_path, 'xcodeproj.bazelrc') if os.path.isfile(xcodeproj_bazelrc): diff --git a/build-system/bazel-rules/rules_xcodeproj b/build-system/bazel-rules/rules_xcodeproj index dc226d129ac..7f600ddd7cb 160000 --- a/build-system/bazel-rules/rules_xcodeproj +++ b/build-system/bazel-rules/rules_xcodeproj @@ -1 +1 @@ -Subproject commit dc226d129aca2237982b98a95c80ed1ccc74f0c5 +Subproject commit 7f600ddd7cb3ebc59c696a4639b84b93267c7b6e diff --git a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh index 50f4990e21e..95404823f23 100755 --- a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh +++ b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh @@ -181,7 +181,7 @@ then echo "$CONFIGURE_FLAGS" > "$CONFIGURED_MARKER" fi - CORE_COUNT=`sysctl -n hw.logicalcpu` + CORE_COUNT=`PATH="$PATH:/usr/sbin" sysctl -n hw.logicalcpu` make -j$CORE_COUNT install $EXPORT || exit 1 popd diff --git a/third-party/yasm/BUILD b/third-party/yasm/BUILD index 38efc12b07e..bc405ece85f 100644 --- a/third-party/yasm/BUILD +++ b/third-party/yasm/BUILD @@ -9,7 +9,7 @@ genrule( cmd_bash = """ set -x - core_count="`sysctl -n hw.logicalcpu`" + core_count=`PATH="$$PATH:/usr/sbin" sysctl -n hw.logicalcpu` BUILD_DIR="$(RULEDIR)/build" rm -rf "$$BUILD_DIR" mkdir -p "$$BUILD_DIR" From e64d88916d24caefd31aaa9b00d5863b1a1b1867 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Apr 2023 21:09:24 +0400 Subject: [PATCH 51/57] Chat wallpaper fixes --- Telegram/Telegram-iOS/en.lproj/Localizable.strings | 5 +++++ .../AttachmentUI/Sources/AttachmentPanel.swift | 1 + .../Sources/Themes/WallpaperGalleryController.swift | 5 ++++- .../Themes/WallpaperGalleryToolbarNode.swift | 2 ++ .../Sources/Themes/WallpaperOptionButtonNode.swift | 3 +++ .../Sources/ChatMessageNotificationItem.swift | 13 +++++++++++-- submodules/TelegramUI/Sources/ChatThemeScreen.swift | 2 +- 7 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 49d73708e2c..d3c26358da5 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9318,3 +9318,8 @@ Sorry for the inconvenience."; "FolderLinkPreview.ListSelectionSelectAllStaticPartSelect" = "ALL"; "FolderLinkPreview.ListSelectionSelectAllStaticPartDeselect" = "ALL"; "FolderLinkPreview.ListSelectionSelectAllFormat" = "{dynamic}{static}"; + +"UserInfo.ChangeWallpaper" = "Change Wallpaper"; + +"Conversation.Theme.PreviewDarkShort" = "Tap to view this theme in the night mode."; +"Conversation.Theme.PreviewLightShort" = "Tap to view this theme in the day mode."; diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 18b04c58033..e62157c9391 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -451,6 +451,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { super.init(pointerStyle: pointerStyle) + self.isExclusiveTouch = true self.clipsToBounds = true self.addSubnode(self.backgroundAnimationNode) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift index 5352a07f7ad..2fa114ef659 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift @@ -515,8 +515,11 @@ public class WallpaperGalleryController: ViewController { var dismissed = false toolbarNode.done = { [weak self] in if let strongSelf = self, !dismissed { - dismissed = true if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { + if centralItemNode.cropNode.scrollNode.view.isDecelerating { + return + } + dismissed = true let options = centralItemNode.options if !strongSelf.entries.isEmpty { let entry = strongSelf.entries[centralItemNode.index] diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift index af9b56b74b6..52ed12bfe95 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift @@ -87,6 +87,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { self.doneButtonSolidTitleNode.isUserInteractionEnabled = false super.init() + + self.doneButton.isExclusiveTouch = true self.addSubnode(self.doneButtonBackgroundNode) self.addSubnode(self.doneButtonTitleNode) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index beebc7850e3..444e2382b1d 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -170,6 +170,8 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { super.init() + self.isExclusiveTouch = true + self.addSubnode(self.backgroundNode) self.addSubnode(self.iconNode) self.addSubnode(self.textNode) @@ -320,6 +322,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { self.clipsToBounds = true self.cornerRadius = 14.0 + self.isExclusiveTouch = true switch value { case let .check(selected): diff --git a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift index 311ab53b930..4e6047bd580 100644 --- a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift @@ -314,8 +314,17 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { title = "📅 \(currentTitle)" } - if let attribute = item.messages.first?.attributes.first(where: { $0 is NotificationInfoMessageAttribute }) as? NotificationInfoMessageAttribute, attribute.flags.contains(.muted), let currentTitle = title { - title = "\(currentTitle) 🔕" + if let message, item.messages.first, let attribute = message.attributes.first(where: { $0 is NotificationInfoMessageAttribute }) as? NotificationInfoMessageAttribute, attribute.flags.contains(.muted), let currentTitle = title { + var isAction = false + for media in message.media { + if media is TelegramMediaAction { + isAction = true + break + } + } + if !isAction { + title = "\(currentTitle) 🔕" + } } let textFont = compact ? Font.regular(15.0) : Font.regular(16.0) diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index bb157f8e4a2..f53617080e5 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -1261,7 +1261,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega if let strongSelf = self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 { strongSelf.displayedPreviewTooltip = true - strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLight(strongSelf.peerName).string : strongSelf.presentationData.strings.Conversation_Theme_PreviewDark(strongSelf.peerName).string, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLightShort : strongSelf.presentationData.strings.Conversation_Theme_PreviewDarkShort, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) From 293e7b75789a2f9642fb843aa0e7259c2ebfb9ab Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Apr 2023 21:11:08 +0400 Subject: [PATCH 52/57] Fix build --- submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift index 4e6047bd580..6b8b5f050eb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift @@ -314,7 +314,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { title = "📅 \(currentTitle)" } - if let message, item.messages.first, let attribute = message.attributes.first(where: { $0 is NotificationInfoMessageAttribute }) as? NotificationInfoMessageAttribute, attribute.flags.contains(.muted), let currentTitle = title { + if let message = item.messages.first, let attribute = message.attributes.first(where: { $0 is NotificationInfoMessageAttribute }) as? NotificationInfoMessageAttribute, attribute.flags.contains(.muted), let currentTitle = title { var isAction = false for media in message.media { if media is TelegramMediaAction { From ed13fd8134f3815f524f3cee64781eb5580a7329 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 12 Apr 2023 22:06:24 +0400 Subject: [PATCH 53/57] Use 9.0.0 as a marker for the direct connection experiment --- .../Sources/OngoingCallContext.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index 92a1e664d1d..bb60b75c2bf 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -934,18 +934,18 @@ public final class OngoingCallContext { } var directConnection: OngoingCallDirectConnection? - #if DEBUG - if #available(iOS 12.0, *) { - for connection in filteredConnections { - if connection.username == "reflector" && connection.reflectorId == 1 && !connection.hasTcp && connection.hasTurn { - directConnection = CallDirectConnectionImpl(host: connection.ip, port: Int(connection.port), peerTag: dataWithHexString(connection.password)) - break + if version == "9.0.0" { + if #available(iOS 12.0, *) { + for connection in filteredConnections { + if connection.username == "reflector" && connection.reflectorId == 1 && !connection.hasTcp && connection.hasTurn { + directConnection = CallDirectConnectionImpl(host: connection.ip, port: Int(connection.port), peerTag: dataWithHexString(connection.password)) + break + } } } + } else { + directConnection = nil } - #else - directConnection = nil - #endif let context = OngoingCallThreadLocalContextWebrtc( version: version, From 1d6d5d53a8235f653fa8152a38d85f2d8a651456 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 12 Apr 2023 22:10:42 +0400 Subject: [PATCH 54/57] Update tgcalls --- submodules/TgVoipWebrtc/tgcalls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index a04d4e182a7..1e528141ed2 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit a04d4e182a77977b30b82f648a589d77b63ce74c +Subproject commit 1e528141ed28f188b9b6dc721c8b630541dfb1b0 From c7803123e8f19cd52bf51501ff02cbf45b41eb57 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 12 Apr 2023 22:58:18 +0400 Subject: [PATCH 55/57] Update localization --- Telegram/Telegram-iOS/en.lproj/Localizable.strings | 4 ++++ submodules/ChatListUI/Sources/ChatListController.swift | 3 +-- .../ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift | 5 ++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d3c26358da5..4a7d0d41daa 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9323,3 +9323,7 @@ Sorry for the inconvenience."; "Conversation.Theme.PreviewDarkShort" = "Tap to view this theme in the night mode."; "Conversation.Theme.PreviewLightShort" = "Tap to view this theme in the day mode."; + +"ChatList.EmptyListContactsHeader" = "YOUR CONTACTS ON TELEGRAM"; +"ChatList.EmptyListContactsHeaderHide" = "hide"; +"ChatList.EmptyListTooltip" = "Send a message or\nstart a group here."; diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index a571f136030..087002b3b69 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2016,8 +2016,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { if let rightButtonView = componentView.rightButtonView { let absoluteFrame = rightButtonView.convert(rightButtonView.bounds, to: self.view) - //TODO:localize - let text: String = "Send a message or\nstart a group here." + let text: String = self.presentationData.strings.ChatList_EmptyListTooltip let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0)) self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in diff --git a/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift index 78f28d14009..e7068be4128 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift @@ -251,10 +251,9 @@ class ChatListSectionHeaderNode: ListViewItemNode { strongSelf.addSubnode(headerNode) } - //TODO:localize - headerNode.title = "YOUR CONTACTS ON TELEGRAM" + headerNode.title = item.strings.ChatList_EmptyListContactsHeader if item.hide != nil { - headerNode.action = "hide" + headerNode.action = item.strings.ChatList_EmptyListContactsHeaderHide headerNode.actionType = .generic headerNode.activateAction = { guard let self else { From 32245285db0fe0b4031e836b124a2b4ad9586b65 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 12 Apr 2023 23:29:33 +0400 Subject: [PATCH 56/57] Fix ad to same chat navigation --- .../TelegramUI/Sources/ChatController.swift | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index cd6de0c56db..45f00f07b92 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4298,22 +4298,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch adAttribute.target { case let .peer(id, messageId, startParam): - let navigationData: ChatControllerInteractionNavigateToPeer - if let bot = message.author as? TelegramUser, bot.botInfo != nil, let startParam = startParam { - navigationData = .withBotStartPayload(ChatControllerInitialBotStart(payload: startParam, behavior: .interactive)) + if case let .peer(currentPeerId) = self.chatLocation, currentPeerId == id { + if let messageId { + self.navigateToMessage(from: nil, to: .id(messageId, nil), rememberInStack: false) + } } else { - var subject: ChatControllerSubject? - if let messageId = messageId { - subject = .message(id: .id(messageId), highlight: true, timecode: nil) + let navigationData: ChatControllerInteractionNavigateToPeer + if let bot = message.author as? TelegramUser, bot.botInfo != nil, let startParam = startParam { + navigationData = .withBotStartPayload(ChatControllerInitialBotStart(payload: startParam, behavior: .interactive)) + } else { + var subject: ChatControllerSubject? + if let messageId = messageId { + subject = .message(id: .id(messageId), highlight: true, timecode: nil) + } + navigationData = .chat(textInputState: nil, subject: subject, peekData: nil) } - navigationData = .chat(textInputState: nil, subject: subject, peekData: nil) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: id)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let self, let peer = peer { + self.openPeer(peer: peer, navigation: navigationData, fromMessage: nil) + } + }) } - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: id)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let self, let peer = peer { - self.openPeer(peer: peer, navigation: navigationData, fromMessage: nil) - } - }) case let .join(_, joinHash): self.controllerInteraction?.openJoinLink(joinHash) } From 888ec6b5cc782f01e17dfcbce0b7d4ab47a7f8b2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Apr 2023 23:37:22 +0400 Subject: [PATCH 57/57] Chat wallpaper fixes --- .../Sources/AttachmentController.swift | 19 ++++++++++-- .../Sources/AttachmentPanel.swift | 6 +++- .../GalleryUI/Sources/GalleryPagerNode.swift | 2 +- .../Sources/MediaPickerScreen.swift | 2 ++ .../Themes/ThemeAccentColorController.swift | 7 ++--- .../ThemeAccentColorControllerNode.swift | 7 ++++- .../Themes/ThemeColorsGridController.swift | 6 ++-- .../Sources/Themes/WallpaperGalleryItem.swift | 15 ++++++++-- .../TelegramUI/Sources/ChatController.swift | 16 ++++++---- ...hatMessageWallpaperBubbleContentNode.swift | 29 +++++++++++++++++-- .../Sources/PeerInfo/PeerInfoScreen.swift | 4 +-- 11 files changed, 86 insertions(+), 27 deletions(-) diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 504bcf075ae..8edb1258764 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -183,6 +183,7 @@ public class AttachmentController: ViewController { private let fromMenu: Bool private let hasTextInput: Bool private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? + public var animateAppearance: Bool = false public var willDismiss: () -> Void = {} public var didDismiss: () -> Void = {} @@ -619,14 +620,26 @@ public class AttachmentController: ViewController { private var animating = false func animateIn() { - guard let layout = self.validLayout else { + guard let layout = self.validLayout, let controller = self.controller else { return } self.animating = true if case .regular = layout.metrics.widthClass { - self.animating = false - + if controller.animateAppearance { + let targetPosition = self.position + let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height) + + self.position = startPosition + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + transition.animateView(allowUserInteraction: true, { + self.position = targetPosition + }, completion: { _ in + self.animating = false + }) + } else { + self.animating = false + } ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 0.1) } else { ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index e62157c9391..257d60b6d0c 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1469,7 +1469,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: buttonSize.height + insets.bottom)) - let containerTransition: ContainedViewLayoutTransition + var containerTransition: ContainedViewLayoutTransition let containerFrame: CGRect if isButtonVisible { var height: CGFloat @@ -1520,6 +1520,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } } + if self.containerNode.frame.size.width.isZero { + containerTransition = .immediate + } + containerTransition.updateFrame(node: self.containerNode, frame: containerFrame) containerTransition.updateFrame(node: self.backgroundNode, frame: containerBounds) self.backgroundNode.update(size: containerBounds.size, transition: transition) diff --git a/submodules/GalleryUI/Sources/GalleryPagerNode.swift b/submodules/GalleryUI/Sources/GalleryPagerNode.swift index 07af78c66f6..fbb87ce7c98 100644 --- a/submodules/GalleryUI/Sources/GalleryPagerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryPagerNode.swift @@ -585,7 +585,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest let node = self.itemNodes[i] transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height))) - let screenFrame = node.convert(node.bounds, to: self.supernode) + let screenFrame = node.view.convert(node.view.bounds, to: self.view.superview) node.screenFrameUpdated(screenFrame) } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 97c3a39a9de..f13bd2e941c 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2022,12 +2022,14 @@ public func wallpaperMediaPickerController( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, + animateAppearance: Bool, completion: @escaping (PHAsset) -> Void = { _ in }, openColors: @escaping () -> Void ) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil }) + controller.animateAppearance = animateAppearance //controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) controller.requestController = { [weak controller] _, present in let presentationData = context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift index 74405b65f18..0dcdee3f627 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift @@ -119,11 +119,8 @@ final class ThemeAccentColorController: ViewController { } else { self.navigationItem.titleView = self.segmentedTitleView } - if case .peer = resultMode { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) - } else { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) - } + + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) } required init(coder aDecoder: NSCoder) { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 12d8f61d15c..2044e541f87 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -313,6 +313,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate doneButtonType = .set } self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.theme, strings: self.presentationData.strings, doneButtonType: doneButtonType) + self.toolbarNode.dark = true self.toolbarNode.setDoneIsSolid(true, transition: .immediate) self.maskNode = ASImageNode() @@ -1177,12 +1178,16 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let colorPanelHeight = max(standardInputHeight, layout.inputHeight ?? 0.0) - bottomInset + inputFieldPanelHeight var colorPanelOffset: CGFloat = 0.0 + var colorPanelY = layout.size.height - bottomInset - colorPanelHeight if self.state.colorPanelCollapsed { colorPanelOffset = colorPanelHeight + colorPanelY = layout.size.height } - let colorPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset - colorPanelHeight + colorPanelOffset), size: CGSize(width: layout.size.width, height: colorPanelHeight)) + let colorPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: colorPanelY), size: CGSize(width: layout.size.width, height: colorPanelHeight)) bottomInset += (colorPanelHeight - colorPanelOffset) + self.toolbarNode.setDoneIsSolid(!self.state.colorPanelCollapsed, transition: transition) + if bottomInset + navigationBarHeight > bounds.height { return } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift index 36a5493534a..75ec7e4f8a5 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift @@ -146,7 +146,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina private var previousContentOffset: GridNodeVisibleContentOffset? - fileprivate let mainButtonStatePromise = Promise(nil) + fileprivate var mainButtonState: AttachmentMainButtonState? var pushController: (ViewController) -> Void = { _ in } var dismissControllers: (() -> Void)? @@ -193,7 +193,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina self?.push(controller) } - self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true))) + self.mainButtonState = AttachmentMainButtonState(text: self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true) } required public init(coder aDecoder: NSCoder) { @@ -362,7 +362,7 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext { } public var mainButtonState: Signal { - return self.controller?.mainButtonStatePromise.get() ?? .single(nil) + return .single(self.controller?.mainButtonState) } init(controller: ThemeColorsGridController) { diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index a05ca062e67..4e751c18454 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -960,8 +960,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { var image = isBlurrable ? image : nil if let imageToScale = image { let actualSize = CGSize(width: imageToScale.size.width * imageToScale.scale, height: imageToScale.size.height * imageToScale.scale) - if actualSize.width > 1280.0 || actualSize.height > 1280.0 { - image = TGScaleImageToPixelSize(image, actualSize.fitted(CGSize(width: 1280.0, height: 1280.0))) + if actualSize.width > 960.0 || actualSize.height > 960.0 { + image = TGScaleImageToPixelSize(image, actualSize.fitted(CGSize(width: 960.0, height: 960.0))) } } strongSelf.blurredNode.image = image @@ -1092,11 +1092,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } @objc func toggleBlur() { + guard !self.animatingBlur else { + return + } let value = !self.blurButtonNode.isSelected self.blurButtonNode.setSelected(value, animated: true) self.setBlurEnabled(value, animated: true) } + private var animatingBlur = false func setBlurEnabled(_ enabled: Bool, animated: Bool) { let blurRadius: CGFloat = 30.0 @@ -1114,19 +1118,24 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } if animated { + self.animatingBlur = true self.blurredNode.blurView.blurRadius = 0.0 UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: { self.blurredNode.blurView.blurRadius = blurRadius - }, completion: nil) + }, completion: { _ in + self.animatingBlur = false + }) } else { self.blurredNode.blurView.blurRadius = blurRadius } } else { if self.blurredNode.supernode != nil { if animated { + self.animatingBlur = true UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: { self.blurredNode.blurView.blurRadius = 0.0 }, completion: { finished in + self.animatingBlur = false if finished { self.blurredNode.removeFromSupernode() } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index cd6de0c56db..50db88d5ef4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -18543,6 +18543,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } public func presentThemeSelection() { + guard self.themeScreen == nil else { + return + } let context = self.context let peerId = self.chatLocation.peerId @@ -18618,8 +18621,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G navigationController.setViewControllers(controllers, animated: true) } } - var openWallpaperPickerImpl: (() -> Void)? - let openWallpaperPicker = { [weak self] in + var openWallpaperPickerImpl: ((Bool) -> Void)? + let openWallpaperPicker = { [weak self] animateAppearance in guard let strongSelf = self else { return } @@ -18627,6 +18630,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), + animateAppearance: animateAppearance, completion: { [weak self] asset in guard let strongSelf = self else { return @@ -18636,7 +18640,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G controller.apply = { [weak self] wallpaper, options, editedImage, cropRect, brightness in if let strongSelf = self { uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: peerId, completion: { - dismissControllers() + Queue.mainQueue().after(0.3, { + dismissControllers() + }) }) } } @@ -18651,7 +18657,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.push(controller) } }, openGallery: { - openWallpaperPickerImpl?() + openWallpaperPickerImpl?(false) }) controller.navigationPresentation = .flatModal strongSelf.push(controller) @@ -18661,7 +18667,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.push(controller) } openWallpaperPickerImpl = openWallpaperPicker - openWallpaperPicker() + openWallpaperPicker(true) }, resetWallpaper: { [weak self] in guard let strongSelf = self, let peerId else { diff --git a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift index 2423b084551..853caa832f7 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -286,14 +286,37 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { let boundingSize = imageSize var imageSize = boundingSize let updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> + var patternArguments: PatternWallpaperArguments? switch media.content { - case let .file(file, _, _, _, _, _): + case let .file(file, patternColors, rotation, intensity, _, _): var representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(item.message), media: file).resourceReference($0.resource)) }) if file.mimeType == "image/svg+xml" || file.mimeType == "application/x-tgwallpattern" { representations.append(ImageRepresentationWithReference(representation: .init(dimensions: PixelDimensions(width: 1440, height: 2960), resource: file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: AnyMediaReference.message(message: MessageReference(item.message), media: file).resourceReference(file.resource))) + + var colors: [UIColor] = [] + var customPatternColor: UIColor? = nil + var bakePatternAlpha: CGFloat = 1.0 + if let intensity = intensity, intensity < 0 { + if patternColors.isEmpty { + colors.append(UIColor(rgb: 0xd6e2ee, alpha: 0.5)) + } else { + colors.append(contentsOf: patternColors.map(UIColor.init(rgb:))) + } + customPatternColor = UIColor(white: 0.0, alpha: 1.0 - CGFloat(abs(intensity))) + } else { + if patternColors.isEmpty { + colors.append(UIColor(rgb: 0xd6e2ee, alpha: 0.5)) + } else { + colors.append(contentsOf: patternColors.map(UIColor.init(rgb:))) + } + let isLight = UIColor.average(of: patternColors.map(UIColor.init(rgb:))).hsb.b > 0.3 + customPatternColor = isLight ? .black : .white + bakePatternAlpha = CGFloat(intensity ?? 50) / 100.0 + } + patternArguments = PatternWallpaperArguments(colors: colors, rotation: rotation, customPatternColor: customPatternColor, bakePatternAlpha: bakePatternAlpha) } if ["image/png", "image/svg+xml", "application/x-tgwallpattern"].contains(file.mimeType) { - updateImageSignal = patternWallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, representations: representations, mode: .screen) + updateImageSignal = patternWallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, representations: representations, mode: .thumbnail) |> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in if let value { return .single(value) @@ -322,7 +345,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.imageNode.setSignal(updateImageSignal, attemptSynchronously: synchronousLoads) - let arguments = TransformImageArguments(corners: ImageCorners(radius: boundingSize.width / 2.0), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: ImageCorners(radius: boundingSize.width / 2.0), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), custom: patternArguments) let apply = makeImageLayout(arguments) apply() diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index a095843d44f..27f6840b404 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4763,8 +4763,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } if let user = peer as? TelegramUser { - if user.botInfo == nil && strongSelf.data?.encryptionKeyFingerprint == nil { - items.append(.action(ContextMenuActionItem(text: "Change Background", icon: { theme in + if user.botInfo == nil && strongSelf.data?.encryptionKeyFingerprint == nil && !user.isDeleted { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ChangeWallpaper, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) }, action: { _, f in f(.dismissWithoutContent)