diff --git a/Telegram/NotificationContent/NotificationViewController.swift b/Telegram/NotificationContent/NotificationViewController.swift index 40a9f422d4..935bdaf770 100644 --- a/Telegram/NotificationContent/NotificationViewController.swift +++ b/Telegram/NotificationContent/NotificationViewController.swift @@ -38,7 +38,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in + self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil), useBetaFeatures: !buildConfig.isAppStoreBuild), setPreferredContentSize: { [weak self] size in self?.preferredContentSize = size }) } diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 9bf3cf7672..794b4bcdc1 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) + 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 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/Share/ShareRootController.swift b/Telegram/Share/ShareRootController.swift index 2574c871e3..fb6b535e4a 100644 --- a/Telegram/Share/ShareRootController.swift +++ b/Telegram/Share/ShareRootController.swift @@ -45,7 +45,7 @@ class ShareRootController: UIViewController { let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in + self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil), useBetaFeatures: !buildConfig.isAppStoreBuild), getExtensionContext: { [weak self] in return self?.extensionContext }) } diff --git a/Telegram/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift index 23670cbb39..17641dfdfd 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), 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), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters) |> mapToSignal { account -> Signal in if let account = account { switch account { diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 62a9ed76e5..23eb11f7a3 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9041,3 +9041,14 @@ Sorry for the inconvenience."; "Premium.Gift.TitleShort" = "Telegram Premium"; "VoiceOver.GiftPremium" = "Gift Telegram Premium"; + +"Login.Email.CantAccess" = "Can't access this email?"; +"Login.Email.ResetTitle" = "Reset Email"; +"Login.Email.ResetText" = "You can change your login email if you are logged into Telegram from another device. Otherwise, if you don't have access to email %@, you can reset this email with an SMS code in 7 days."; +"Login.Email.Reset" = "Reset"; +"Login.Email.ResetNowViaSMS" = "Reset now via SMS"; +"Login.Email.WillBeResetIn" = "Email will be reset in %@"; +"Login.Email.PremiumRequiredTitle" = "Telegram Premium Required"; +"Login.Email.PremiumRequiredText" = "Due to high cost of SMS in your country, you need to have a **Telegram Premium** account to reset this email via an SMS code. You can ask a friend to a gift a Premium subscription for your account %@"; + +"ChatList.StartMessaging" = "Select a chat to start messaging"; diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index ff22529156..840fce621a 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -433,7 +433,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { let containerFrame: CGRect let clipFrame: CGRect let containerScale: CGFloat - if layout.metrics.widthClass == .compact { + if case .compact = layout.metrics.widthClass { self.clipNode.clipsToBounds = true if isLandscape { diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index c6d0ea6110..febbaaa2a5 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -4811,7 +4811,7 @@ private final class ChatListLocationContext { strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, - content: .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil), + content: .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true), tapped: { [weak self] in guard let self else { return diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index fc82b25d61..e84bdebc74 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -89,7 +89,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { case experimentalCompatibility(Bool) case enableDebugDataDisplay(Bool) case acceleratedStickers(Bool) - case experimentalBackground(Bool) case inlineForums(Bool) case localTranscription(Bool) case enableReactionOverrides(Bool) @@ -118,7 +117,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineForums, .localTranscription, .enableReactionOverrides, .restorePurchases: + case .clearTips, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .inlineForums, .localTranscription, .enableReactionOverrides, .restorePurchases: return DebugControllerSection.experiments.rawValue case .logTranslationRecognition, .resetTranslationStates: return DebugControllerSection.translation.rawValue @@ -201,8 +200,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 34 case .acceleratedStickers: return 35 - case .experimentalBackground: - return 36 case .inlineForums: return 37 case .localTranscription: @@ -1186,16 +1183,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) - case let .experimentalBackground(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Background Experiment", value: value, sectionId: self.section, style: .blocks, updated: { value in - let _ = arguments.sharedContext.accountManager.transaction ({ transaction in - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in - var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings - settings.experimentalBackground = value - return PreferencesEntry(settings) - }) - }).start() - }) case let .inlineForums(value): return ItemListSwitchItem(presentationData: presentationData, title: "Inline Forums", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in @@ -1324,7 +1311,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { } } -private func debugControllerEntries(sharedContext: SharedAccountContext, presentationData: PresentationData, loggingSettings: LoggingSettings, mediaInputSettings: MediaInputSettings, experimentalSettings: ExperimentalUISettings, networkSettings: NetworkSettings?, hasLegacyAppData: Bool) -> [DebugControllerEntry] { +private func debugControllerEntries(sharedContext: SharedAccountContext, presentationData: PresentationData, loggingSettings: LoggingSettings, mediaInputSettings: MediaInputSettings, experimentalSettings: ExperimentalUISettings, networkSettings: NetworkSettings?, hasLegacyAppData: Bool, useBetaFeatures: Bool) -> [DebugControllerEntry] { var entries: [DebugControllerEntry] = [] let isMainApp = sharedContext.applicationBindings.isMainApp @@ -1374,7 +1361,6 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility)) entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay)) entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers)) - entries.append(.experimentalBackground(experimentalSettings.experimentalBackground)) entries.append(.inlineForums(experimentalSettings.inlineForums)) entries.append(.localTranscription(experimentalSettings.localTranscription)) if case .internal = sharedContext.applicationBindings.appBuildType { @@ -1404,7 +1390,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present if isMainApp { entries.append(.disableVideoAspectScaling(experimentalSettings.disableVideoAspectScaling)) - entries.append(.enableNetworkFramework(networkSettings?.useNetworkFramework ?? false)) + entries.append(.enableNetworkFramework(networkSettings?.useNetworkFramework ?? useBetaFeatures)) } if let backupHostOverride = networkSettings?.backupHostOverride { @@ -1476,8 +1462,13 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun }) } + var useBetaFeatures: Bool = false + if let context { + useBetaFeatures = context.account.network.useBetaFeatures + } + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Debug"), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: debugControllerEntries(sharedContext: sharedContext, presentationData: presentationData, loggingSettings: loggingSettings, mediaInputSettings: mediaInputSettings, experimentalSettings: experimentalSettings, networkSettings: networkSettings, hasLegacyAppData: hasLegacyAppData), style: .blocks) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: debugControllerEntries(sharedContext: sharedContext, presentationData: presentationData, loggingSettings: loggingSettings, mediaInputSettings: mediaInputSettings, experimentalSettings: experimentalSettings, networkSettings: networkSettings, hasLegacyAppData: hasLegacyAppData, useBetaFeatures: useBetaFeatures), style: .blocks) return (controllerState, (listState, arguments)) } diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index 88dffb0d48..4668c529a8 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -34,11 +34,6 @@ public struct NavigationAnimationOptions : OptionSet { public static let removeOnMasterDetails = NavigationAnimationOptions(rawValue: 1 << 0) } -public enum NavigationEmptyDetailsBackgoundMode { - case image(UIImage) - case wallpaper(UIImage) -} - private enum ControllerTransition { case none case appearance @@ -120,6 +115,10 @@ public final class NavigationControllerDropContent { } } +public protocol NavigationDetailsPlaceholderNode: ASDisplayNode { + func updateLayout(size: CGSize, needsTiling: Bool, transition: ContainedViewLayoutTransition) +} + open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { public var isOpaqueWhenInOverlay: Bool = true public var blocksBackgroundWhenInOverlay: Bool = true @@ -131,7 +130,6 @@ open class NavigationController: UINavigationController, ContainableController, } private var masterDetailsBlackout: MasterDetailLayoutBlackout? - private var backgroundDetailsMode: NavigationEmptyDetailsBackgoundMode? public var lockOrientation: Bool = false @@ -232,16 +230,21 @@ open class NavigationController: UINavigationController, ContainableController, self.requestLayout(transition: transition) } - public func updateBackgroundDetailsMode(_ mode: NavigationEmptyDetailsBackgoundMode?, transition: ContainedViewLayoutTransition) { - self.backgroundDetailsMode = mode - self.requestLayout(transition: transition) + private weak var detailsPlaceholderNode: NavigationDetailsPlaceholderNode? + public func updateDetailsPlaceholderNode(_ node: NavigationDetailsPlaceholderNode?) { + if self.detailsPlaceholderNode !== node { + self.detailsPlaceholderNode?.removeFromSupernode() + self.detailsPlaceholderNode = node + if let node { + self.displayNode.insertSubnode(node, at: 0) + } + } } - public init(mode: NavigationControllerMode, theme: NavigationControllerTheme, isFlat: Bool = false, backgroundDetailsMode: NavigationEmptyDetailsBackgoundMode? = nil) { + public init(mode: NavigationControllerMode, theme: NavigationControllerTheme, isFlat: Bool = false) { self.mode = mode self.theme = theme self.isFlat = isFlat - self.backgroundDetailsMode = backgroundDetailsMode super.init(nibName: nil, bundle: nil) } @@ -340,7 +343,7 @@ open class NavigationController: UINavigationController, ContainableController, return nil } - public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { if !self.isViewLoaded { self.loadView() } @@ -836,7 +839,11 @@ open class NavigationController: UINavigationController, ContainableController, flatContainer.keyboardViewManager = nil flatContainer.canHaveKeyboardFocus = false } - self.displayNode.insertSubnode(flatContainer, at: 0) + if let detailsPlaceholderNode = self.detailsPlaceholderNode { + self.displayNode.insertSubnode(flatContainer, aboveSubnode: detailsPlaceholderNode) + } else { + self.displayNode.insertSubnode(flatContainer, at: 0) + } self.rootContainer = .flat(flatContainer) flatContainer.frame = CGRect(origin: CGPoint(), size: layout.size) flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: .immediate) @@ -859,7 +866,11 @@ open class NavigationController: UINavigationController, ContainableController, flatContainer.keyboardViewManager = nil flatContainer.canHaveKeyboardFocus = false } - self.displayNode.insertSubnode(flatContainer, at: 0) + if let detailsPlaceholderNode = self.detailsPlaceholderNode { + self.displayNode.insertSubnode(flatContainer, aboveSubnode: detailsPlaceholderNode) + } else { + self.displayNode.insertSubnode(flatContainer, at: 0) + } self.rootContainer = .flat(flatContainer) flatContainer.frame = CGRect(origin: CGPoint(), size: layout.size) flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: .immediate) @@ -873,7 +884,11 @@ open class NavigationController: UINavigationController, ContainableController, }, scrollToTop: { [weak self] subject in self?.scrollToTop(subject) }) - self.displayNode.insertSubnode(splitContainer, at: 0) + if let detailsPlaceholderNode = self.detailsPlaceholderNode { + self.displayNode.insertSubnode(splitContainer, aboveSubnode: detailsPlaceholderNode) + } else { + self.displayNode.insertSubnode(splitContainer, at: 0) + } self.rootContainer = .split(splitContainer) if previousModalContainer == nil { splitContainer.canHaveKeyboardFocus = true @@ -881,7 +896,7 @@ open class NavigationController: UINavigationController, ContainableController, splitContainer.canHaveKeyboardFocus = false } splitContainer.frame = CGRect(origin: CGPoint(), size: layout.size) - splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: .immediate) + splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, detailsPlaceholderNode: self.detailsPlaceholderNode, transition: .immediate) flatContainer.statusBarStyleUpdated = nil flatContainer.removeFromSupernode() case let .split(splitContainer): @@ -891,7 +906,7 @@ open class NavigationController: UINavigationController, ContainableController, splitContainer.canHaveKeyboardFocus = false } transition.updateFrame(node: splitContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) - splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: transition) + splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, detailsPlaceholderNode: self.detailsPlaceholderNode, transition: transition) } } else { let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in @@ -899,7 +914,11 @@ open class NavigationController: UINavigationController, ContainableController, }, scrollToTop: { [weak self] subject in self?.scrollToTop(subject) }) - self.displayNode.insertSubnode(splitContainer, at: 0) + if let detailsPlaceholderNode = self.detailsPlaceholderNode { + self.displayNode.insertSubnode(splitContainer, aboveSubnode: detailsPlaceholderNode) + } else { + self.displayNode.insertSubnode(splitContainer, at: 0) + } self.rootContainer = .split(splitContainer) if previousModalContainer == nil { splitContainer.canHaveKeyboardFocus = true @@ -907,7 +926,7 @@ open class NavigationController: UINavigationController, ContainableController, splitContainer.canHaveKeyboardFocus = false } splitContainer.frame = CGRect(origin: CGPoint(), size: layout.size) - splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: .immediate) + splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, detailsPlaceholderNode: self.detailsPlaceholderNode, transition: .immediate) } } diff --git a/submodules/Display/Source/Navigation/NavigationSplitContainer.swift b/submodules/Display/Source/Navigation/NavigationSplitContainer.swift index 809df60a31..1f1f795837 100644 --- a/submodules/Display/Source/Navigation/NavigationSplitContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationSplitContainer.swift @@ -83,7 +83,7 @@ final class NavigationSplitContainer: ASDisplayNode { self.separator.backgroundColor = theme.navigationBar.separatorColor } - func update(layout: ContainerViewLayout, masterControllers: [ViewController], detailControllers: [ViewController], transition: ContainedViewLayoutTransition) { + func update(layout: ContainerViewLayout, masterControllers: [ViewController], detailControllers: [ViewController], detailsPlaceholderNode: NavigationDetailsPlaceholderNode?, transition: ContainedViewLayoutTransition) { let masterWidth: CGFloat = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) let detailWidth = layout.size.width - masterWidth @@ -94,6 +94,12 @@ final class NavigationSplitContainer: ASDisplayNode { transition.updateFrame(node: self.detailContainer, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height))) transition.updateFrame(node: self.separator, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) + if let detailsPlaceholderNode { + let needsTiling = layout.size.width > layout.size.height + detailsPlaceholderNode.updateLayout(size: CGSize(width: detailWidth, height: layout.size.height), needsTiling: needsTiling, transition: transition) + transition.updateFrame(node: detailsPlaceholderNode, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height))) + } + self.masterContainer.update(layout: ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: false, controllers: masterControllers, transition: transition) self.detailContainer.update(layout: ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: true, controllers: detailControllers, transition: transition) diff --git a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift index 59b5e3965f..b5e3fe6c42 100644 --- a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift +++ b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift @@ -271,6 +271,22 @@ public final class GradientBackgroundNode: ASDisplayNode { private var patternOverlayLayer: GradientBackgroundPatternOverlayLayer? + private class SharedAnimationUpdate { + let phase: Int + let sender: AnyObject + + init( + phase: Int, + sender: AnyObject + ) { + self.phase = phase + self.sender = sender + } + } + + private static let sharedAnimationSyncPipe = ValuePipe() + private var sharedAnimationSyncDisposable: Disposable? + public init(colors: [UIColor]? = nil, useSharedAnimationPhase: Bool = false, adjustSaturation: Bool = true) { self.useSharedAnimationPhase = useSharedAnimationPhase self.saturation = adjustSaturation ? 1.7 : 1.0 @@ -289,12 +305,26 @@ public final class GradientBackgroundNode: ASDisplayNode { if useSharedAnimationPhase { self.phase = GradientBackgroundNode.sharedPhase + + self.sharedAnimationSyncDisposable = (GradientBackgroundNode.sharedAnimationSyncPipe.signal() + |> filter { [weak self] update in + return update.sender !== self + } + |> deliverOnMainQueue).start(next: { [weak self] update in + if let self { + self.phase = update.phase + if let size = self.validLayout { + self.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {}) + } + } + }) } else { self.phase = 0 } } deinit { + self.sharedAnimationSyncDisposable?.dispose() } public func setPatternOverlay(layer: GradientBackgroundPatternOverlayLayer?) { @@ -422,8 +452,7 @@ public final class GradientBackgroundNode: ASDisplayNode { animation.fillMode = .backwards animation.beginTime = self.contentView.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.25 } - - + self.isAnimating = true if let patternOverlayLayer = self.patternOverlayLayer { patternOverlayLayer.isAnimating = true @@ -542,6 +571,8 @@ public final class GradientBackgroundNode: ASDisplayNode { } } } + + public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool, backwards: Bool, completion: @escaping () -> Void) { guard case let .animated(duration, _) = transition, duration > 0.001 else { @@ -560,6 +591,7 @@ public final class GradientBackgroundNode: ASDisplayNode { } if self.useSharedAnimationPhase { GradientBackgroundNode.sharedPhase = self.phase + GradientBackgroundNode.sharedAnimationSyncPipe.putNext(SharedAnimationUpdate(phase: self.phase, sender: self)) } if let size = self.validLayout { self.updateLayout(size: size, transition: transition, extendAnimation: extendAnimation, backwards: backwards, completion: completion) diff --git a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift index 913ff045ad..51037faef1 100644 --- a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift +++ b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift @@ -287,9 +287,7 @@ public class InvisibleInkDustNode: ASDisplayNode { return } self.staticParams = (size, color, lineRects) - - let start = CACurrentMediaTime() - + var combinedRect: CGRect? var combinedRects: [CGRect] = [] for rect in lineRects { @@ -308,7 +306,6 @@ public class InvisibleInkDustNode: ASDisplayNode { combinedRects.append(combinedRect.insetBy(dx: 0.0, dy: -1.0)) } - print("combining \(CACurrentMediaTime() - start)") Queue.concurrentDefaultQueue().async { var generator = ArbitraryRandomNumberGenerator(seed: 1) let image = generateImage(size, rotatedContext: { size, context in @@ -331,8 +328,6 @@ public class InvisibleInkDustNode: ASDisplayNode { } } self.staticNode?.frame = CGRect(origin: CGPoint(), size: size) - - print("total draw \(CACurrentMediaTime() - start)") } } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 770d27ca53..6f3f918bbd 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -523,7 +523,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI self.context = context self.persistentItems = persistentItems - self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground) + self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) self.wallpaperBackgroundNode.backgroundColor = .black self.scrollNode = ASScrollNode() diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index 7e7002806c..266e4593ae 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -1732,9 +1732,14 @@ public final class MediaBox { return } + var lastReportValue = 0 + let reportProgress: (Int) -> Void = { count in - Queue.mainQueue().async { - subscriber.putNext(min(1.0, Float(count) / Float(totalCount))) + let currentProgress = min(1.0, Float(count) / Float(totalCount)) + let currentInteger = Int(currentProgress * 100.0) + if lastReportValue != currentInteger { + lastReportValue = currentInteger + subscriber.putNext(currentProgress) } } @@ -1781,6 +1786,7 @@ public final class MediaBox { self.didRemoveResourcesPipe.putNext(Void()) } + subscriber.putNext(1.0) subscriber.putCompletion() } return EmptyDisposable diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index 8739e0c06f..8faff377d4 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -416,11 +416,19 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { demoSubject = .translation } + let buttonText: String + if let price = state?.price { + buttonText = strings.Premium_Gift_GiftSubscription(price).string + } else { + buttonText = strings.Common_OK + } var dismissImpl: (() -> Void)? - let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .gift(state?.price), order: state?.configuration.perks, buttonText: strings.Premium_Gift_GiftSubscription(state?.price ?? "–").string, isPremium: false) - controller.action = { + let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .gift(state?.price), order: state?.configuration.perks, buttonText: buttonText, isPremium: false) + controller.action = { [weak state] in dismissImpl?() - buy() + if let _ = state?.price { + buy() + } } controller.disposed = { // updateIsFocused(false) diff --git a/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift index 38efaea5a4..d2300cd98f 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift @@ -23,11 +23,10 @@ extension ReactionsMessageAttribute { parsedRecentReactions = recentReactions.compactMap { recentReaction -> ReactionsMessageAttribute.RecentPeer? in switch recentReaction { case let .messagePeerReaction(flags, peerId, date, reaction): - let _ = date let isLarge = (flags & (1 << 0)) != 0 let isUnseen = (flags & (1 << 1)) != 0 if let reaction = MessageReaction.Reaction(apiReaction: reaction) { - return ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: isLarge, isUnseen: isUnseen, peerId: peerId.peerId) + return ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: isLarge, isUnseen: isUnseen, peerId: peerId.peerId, timestamp: date) } else { return nil } @@ -118,7 +117,7 @@ private func mergeReactions(reactions: [MessageReaction], recentPeers: [Reaction if let index = recentPeers.firstIndex(where: { $0.value == pendingReaction.value && $0.peerId == accountPeerId }) { recentPeers.remove(at: index) } - recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: pendingReaction.value, isLarge: false, isUnseen: false, peerId: accountPeerId)) + recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: pendingReaction.value, isLarge: false, isUnseen: false, peerId: accountPeerId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970))) } for i in (0 ..< result.count).reversed() { @@ -186,11 +185,10 @@ extension ReactionsMessageAttribute { parsedRecentReactions = recentReactions.compactMap { recentReaction -> ReactionsMessageAttribute.RecentPeer? in switch recentReaction { case let .messagePeerReaction(flags, peerId, date, reaction): - let _ = date let isLarge = (flags & (1 << 0)) != 0 let isUnseen = (flags & (1 << 1)) != 0 if let reaction = MessageReaction.Reaction(apiReaction: reaction) { - return ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: isLarge, isUnseen: isUnseen, peerId: peerId.peerId) + return ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: isLarge, isUnseen: isUnseen, peerId: peerId.peerId, timestamp: date) } else { return nil } diff --git a/submodules/TelegramCore/Sources/Network/Network.swift b/submodules/TelegramCore/Sources/Network/Network.swift index ae32f7536f..a918255478 100644 --- a/submodules/TelegramCore/Sources/Network/Network.swift +++ b/submodules/TelegramCore/Sources/Network/Network.swift @@ -433,7 +433,9 @@ public struct NetworkInitializationArguments { public let autolockDeadine: Signal public let encryptionProvider: EncryptionProvider public let deviceModelName:String? - public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal, autolockDeadine: Signal, encryptionProvider: EncryptionProvider, deviceModelName:String?) { + public let 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) { self.apiId = apiId self.apiHash = apiHash self.languagesCategory = languagesCategory @@ -444,6 +446,7 @@ public struct NetworkInitializationArguments { self.autolockDeadine = autolockDeadine self.encryptionProvider = encryptionProvider self.deviceModelName = deviceModelName + self.useBetaFeatures = useBetaFeatures } } #if os(iOS) @@ -494,10 +497,21 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa let context = MTContext(serialization: serialization, encryptionProvider: arguments.encryptionProvider, apiEnvironment: apiEnvironment, isTestingEnvironment: testingEnvironment, useTempAuthKeys: useTempAuthKeys) - if let networkSettings = networkSettings, networkSettings.useNetworkFramework { - if #available(iOS 12.0, macOS 10.14, *) { - context.makeTcpConnectionInterface = { delegate, delegateQueue in - return NetworkFrameworkTcpConnectionInterface(delegate: delegate, delegateQueue: delegateQueue) + if let networkSettings = networkSettings { + let useNetworkFramework: Bool + if let customValue = networkSettings.useNetworkFramework { + useNetworkFramework = customValue + } else if arguments.useBetaFeatures { + useNetworkFramework = true + } else { + useNetworkFramework = false + } + + if useNetworkFramework { + if #available(iOS 12.0, macOS 10.14, *) { + context.makeTcpConnectionInterface = { delegate, delegateQueue in + return NetworkFrameworkTcpConnectionInterface(delegate: delegate, delegateQueue: delegateQueue) + } } } } @@ -589,7 +603,7 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa mtProto.delegate = connectionStatusDelegate mtProto.add(requestService) - let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers) + let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers, useBetaFeatures: arguments.useBetaFeatures) appDataUpdatedImpl = { [weak network] data in guard let data = data else { return @@ -720,6 +734,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { let basePath: String private let connectionStatusDelegate: MTProtoConnectionStatusDelegate private let useRequestTimeoutTimers: Bool + public let useBetaFeatures: Bool private let appDataDisposable: Disposable @@ -763,7 +778,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { return "Network context: \(self.context)" } - fileprivate init(queue: Queue, datacenterId: Int, context: MTContext, mtProto: MTProto, requestService: MTRequestMessageService, connectionStatusDelegate: MTProtoConnectionStatusDelegate, _connectionStatus: Promise, basePath: String, appDataDisposable: Disposable, encryptionProvider: EncryptionProvider, useRequestTimeoutTimers: Bool) { + fileprivate init(queue: Queue, datacenterId: Int, context: MTContext, mtProto: MTProto, requestService: MTRequestMessageService, connectionStatusDelegate: MTProtoConnectionStatusDelegate, _connectionStatus: Promise, basePath: String, appDataDisposable: Disposable, encryptionProvider: EncryptionProvider, useRequestTimeoutTimers: Bool, useBetaFeatures: Bool) { self.encryptionProvider = encryptionProvider self.queue = queue @@ -777,6 +792,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { self.appDataDisposable = appDataDisposable self.basePath = basePath self.useRequestTimeoutTimers = useRequestTimeoutTimers + self.useBetaFeatures = useBetaFeatures super.init() diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index 2215e349a6..0ddf59afbe 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -26,7 +26,10 @@ func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force: } } if let fromLargestRepresentation = largestImageRepresentation(fromImage.representations), let toLargestRepresentation = largestImageRepresentation(toImage.representations) { - copyOrMoveResourceData(from: fromLargestRepresentation.resource, to: toLargestRepresentation.resource, mediaBox: postbox.mediaBox) + if fromLargestRepresentation.progressiveSizes != toLargestRepresentation.progressiveSizes { + } else { + copyOrMoveResourceData(from: fromLargestRepresentation.resource, to: toLargestRepresentation.resource, mediaBox: postbox.mediaBox) + } } } else if let fromFile = from as? TelegramMediaFile, let toFile = to as? TelegramMediaFile { if let fromPreview = smallestImageRepresentation(fromFile.previewRepresentations), let toPreview = smallestImageRepresentation(toFile.previewRepresentations) { diff --git a/submodules/TelegramCore/Sources/State/MessageReactions.swift b/submodules/TelegramCore/Sources/State/MessageReactions.swift index 34ddd0a942..ad811dbec4 100644 --- a/submodules/TelegramCore/Sources/State/MessageReactions.swift +++ b/submodules/TelegramCore/Sources/State/MessageReactions.swift @@ -338,7 +338,7 @@ public extension EngineMessageReactionListContext.State { for recentPeer in reactionsAttribute.recentPeers { if let peer = message.peers[recentPeer.peerId] { if reaction == nil || recentPeer.value == reaction { - items.append(EngineMessageReactionListContext.Item(peer: EnginePeer(peer), reaction: recentPeer.value, timestamp: readStats?.readTimestamps[peer.id])) + items.append(EngineMessageReactionListContext.Item(peer: EnginePeer(peer), reaction: recentPeer.value, timestamp: recentPeer.timestamp ?? readStats?.readTimestamps[peer.id])) } } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_NetworkSettings.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_NetworkSettings.swift index 630685355e..14d8a988ba 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_NetworkSettings.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_NetworkSettings.swift @@ -4,13 +4,13 @@ public struct NetworkSettings: Codable { public var reducedBackupDiscoveryTimeout: Bool public var applicationUpdateUrlPrefix: String? public var backupHostOverride: String? - public var useNetworkFramework: Bool + public var useNetworkFramework: Bool? public static var defaultSettings: NetworkSettings { - return NetworkSettings(reducedBackupDiscoveryTimeout: false, applicationUpdateUrlPrefix: nil, backupHostOverride: nil, useNetworkFramework: false) + return NetworkSettings(reducedBackupDiscoveryTimeout: false, applicationUpdateUrlPrefix: nil, backupHostOverride: nil, useNetworkFramework: nil) } - public init(reducedBackupDiscoveryTimeout: Bool, applicationUpdateUrlPrefix: String?, backupHostOverride: String?, useNetworkFramework: Bool) { + public init(reducedBackupDiscoveryTimeout: Bool, applicationUpdateUrlPrefix: String?, backupHostOverride: String?, useNetworkFramework: Bool?) { self.reducedBackupDiscoveryTimeout = reducedBackupDiscoveryTimeout self.applicationUpdateUrlPrefix = applicationUpdateUrlPrefix self.backupHostOverride = backupHostOverride @@ -23,7 +23,7 @@ public struct NetworkSettings: Codable { self.reducedBackupDiscoveryTimeout = ((try? container.decode(Int32.self, forKey: "reducedBackupDiscoveryTimeout")) ?? 0) != 0 self.applicationUpdateUrlPrefix = try? container.decodeIfPresent(String.self, forKey: "applicationUpdateUrlPrefix") self.backupHostOverride = try? container.decodeIfPresent(String.self, forKey: "backupHostOverride") - self.useNetworkFramework = try container.decodeIfPresent(Bool.self, forKey: "useNetworkFramework") ?? NetworkSettings.defaultSettings.useNetworkFramework + self.useNetworkFramework = try container.decodeIfPresent(Bool.self, forKey: "useNetworkFramework_v2") } public func encode(to encoder: Encoder) throws { @@ -32,6 +32,6 @@ public struct NetworkSettings: Codable { try container.encode((self.reducedBackupDiscoveryTimeout ? 1 : 0) as Int32, forKey: "reducedBackupDiscoveryTimeout") try container.encodeIfPresent(self.applicationUpdateUrlPrefix, forKey: "applicationUpdateUrlPrefix") try container.encodeIfPresent(self.backupHostOverride, forKey: "backupHostOverride") - try container.encode(self.useNetworkFramework, forKey: "useNetworkFramework") + try container.encodeIfPresent(self.useNetworkFramework, forKey: "useNetworkFramework_v2") } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift index e17ecd5814..ed23a4220a 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift @@ -119,12 +119,14 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute { public var isLarge: Bool public var isUnseen: Bool public var peerId: PeerId + public var timestamp: Int32? - public init(value: MessageReaction.Reaction, isLarge: Bool, isUnseen: Bool, peerId: PeerId) { + public init(value: MessageReaction.Reaction, isLarge: Bool, isUnseen: Bool, peerId: PeerId, timestamp: Int32?) { self.value = value self.isLarge = isLarge self.isUnseen = isUnseen self.peerId = peerId + self.timestamp = timestamp } public init(decoder: PostboxDecoder) { @@ -136,6 +138,7 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute { self.isLarge = decoder.decodeInt32ForKey("l", orElse: 0) != 0 self.isUnseen = decoder.decodeInt32ForKey("u", orElse: 0) != 0 self.peerId = PeerId(decoder.decodeInt64ForKey("p", orElse: 0)) + self.timestamp = decoder.decodeOptionalInt32ForKey("ts") } public func encode(_ encoder: PostboxEncoder) { @@ -148,6 +151,11 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute { encoder.encodeInt32(self.isLarge ? 1 : 0, forKey: "l") encoder.encodeInt32(self.isUnseen ? 1 : 0, forKey: "u") encoder.encodeInt64(self.peerId.toInt64(), forKey: "p") + if let timestamp = self.timestamp { + encoder.encodeInt32(timestamp, forKey: "ts") + } else { + encoder.encodeNil(forKey: "ts") + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift index 2531804cf5..67a3b370a5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift @@ -379,7 +379,7 @@ func _internal_renderStorageUsageStatsMessages(account: Account, stats: StorageU } } -func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], includeMessages: [Message], excludeMessages: [Message]) -> Signal { +func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], includeMessages: [Message], excludeMessages: [Message]) -> Signal { let mediaBox = account.postbox.mediaBox return Signal { subscriber in var includeResourceIds = Set() @@ -435,7 +435,9 @@ func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories resourceIds.append(MediaResourceId(value)) } } - let _ = mediaBox.removeCachedResources(resourceIds).start(completed: { + let _ = mediaBox.removeCachedResources(resourceIds).start(next: { progress in + subscriber.putNext(progress) + }, completed: { if peerId == nil && categories.contains(.misc) { let additionalPaths: [String] = [ "cache", @@ -469,7 +471,7 @@ func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories } } -func _internal_clearStorage(account: Account, peerIds: Set, includeMessages: [Message], excludeMessages: [Message]) -> Signal { +func _internal_clearStorage(account: Account, peerIds: Set, includeMessages: [Message], excludeMessages: [Message]) -> Signal { let mediaBox = account.postbox.mediaBox return Signal { subscriber in var includeResourceIds = Set() @@ -496,12 +498,15 @@ func _internal_clearStorage(account: Account, peerIds: Set, inclu mediaBox.storageBox.remove(peerIds: peerIds, includeIds: includeIds, excludeIds: excludeIds, completion: { ids in var resourceIds: [MediaResourceId] = [] + for id in ids { if let value = String(data: id, encoding: .utf8) { resourceIds.append(MediaResourceId(value)) } } - let _ = mediaBox.removeCachedResources(resourceIds).start(completed: { + let _ = mediaBox.removeCachedResources(resourceIds).start(next: { progress in + subscriber.putNext(progress) + }, completed: { subscriber.putCompletion() }) }) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index 7f5d0bde23..e71bff8e58 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -233,11 +233,11 @@ public extension TelegramEngine { return _internal_renderStorageUsageStatsMessages(account: self.account, stats: stats, categories: categories, existingMessages: existingMessages) } - public func clearStorage(peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], includeMessages: [Message], excludeMessages: [Message]) -> Signal { + public func clearStorage(peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], includeMessages: [Message], excludeMessages: [Message]) -> Signal { return _internal_clearStorage(account: self.account, peerId: peerId, categories: categories, includeMessages: includeMessages, excludeMessages: excludeMessages) } - public func clearStorage(peerIds: Set, includeMessages: [Message], excludeMessages: [Message]) -> Signal { + public func clearStorage(peerIds: Set, includeMessages: [Message], excludeMessages: [Message]) -> Signal { _internal_clearStorage(account: self.account, peerIds: peerIds, includeMessages: includeMessages, excludeMessages: excludeMessages) } diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 2b7ff829dc..9ee3acfe7c 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -1838,7 +1838,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { strongSelf.reorderItems(category: category, items: items) }, makeSearchContainerNode: { [weak self, weak controllerInteraction] content in - guard let controllerInteraction = controllerInteraction else { + guard let self, let controllerInteraction = controllerInteraction else { return nil } @@ -1860,9 +1860,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { mode: mappedMode, trendingGifsPromise: trendingGifsPromise, cancel: { - } + }, + peekBehavior: self.emojiInputInteraction?.peekBehavior ) - searchContainerNode.openGifContextMenu = { item, sourceNode, sourceRect, gesture, isSaved in + searchContainerNode.openGifContextMenu = { [weak self] item, sourceNode, sourceRect, gesture, isSaved in guard let self else { return } @@ -2502,7 +2503,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { self.present = present } - public func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, EmojiPagerContentComponent.View.ItemLayer, TelegramMediaFile)?) { + public func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, CALayer, TelegramMediaFile)?) { self.viewRecords = self.viewRecords.filter({ $0.view != nil }) let viewRecord = self.viewRecords.first(where: { $0.view === view }) diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift index c645036b57..1772b436da 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift @@ -12,6 +12,7 @@ import EntityKeyboard import ChatControllerInteraction import MultiplexedVideoNode import FeaturedStickersScreen +import StickerPeekUI private let searchBarHeight: CGFloat = 52.0 @@ -37,6 +38,7 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer public private(set) var contentNode: PaneSearchContentNode & ASDisplayNode private let controllerInteraction: ChatControllerInteraction private let inputNodeInteraction: ChatMediaInputNodeInteraction + private let peekBehavior: EmojiContentPeekBehavior? private let backgroundNode: ASDisplayNode private let searchBar: PaneSearchBarNode @@ -51,11 +53,12 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer return self.contentNode.ready } - public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, mode: ChatMediaInputSearchMode, trendingGifsPromise: Promise, cancel: @escaping () -> Void) { + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, mode: ChatMediaInputSearchMode, trendingGifsPromise: Promise, cancel: @escaping () -> Void, peekBehavior: EmojiContentPeekBehavior?) { self.context = context self.mode = mode self.controllerInteraction = controllerInteraction self.inputNodeInteraction = inputNodeInteraction + self.peekBehavior = peekBehavior switch mode { case .gif: self.contentNode = GifPaneSearchContentNode(context: context, theme: theme, strings: strings, controllerInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction, trendingPromise: trendingGifsPromise) @@ -103,6 +106,41 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer self?.openGifContextMenu?(file, node, rect, gesture, isSaved) } } + + if let contentNode = self.contentNode as? StickerPaneSearchContentNode, let peekBehavior = self.peekBehavior { + peekBehavior.setGestureRecognizerEnabled(view: self.contentNode.view, isEnabled: true, itemAtPoint: { [weak contentNode] point in + guard let contentNode else { + return nil + } + guard let (itemNode, item) = contentNode.itemAt(point: point) else { + return nil + } + + var maybeFile: TelegramMediaFile? + if let item = item as? StickerPreviewPeekItem { + switch item { + case let .found(foundItem): + maybeFile = foundItem.file + case let .pack(fileValue): + maybeFile = fileValue + } + } + guard let file = maybeFile else { + return nil + } + + var groupId: AnyHashable = AnyHashable("search") + for attribute in file.attributes { + if case let .Sticker(_, packReference, _) = attribute { + if case let .id(id, _) = packReference { + groupId = AnyHashable(ItemCollectionId(namespace: Namespaces.ItemCollection.CloudStickerPacks, id: id)) + } + } + } + + return (groupId, itemNode.layer, file) + }) + } } public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 0c53822d39..d8eff66339 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -32,14 +32,14 @@ public enum ChatTitleContent: Equatable { case replies } - case peer(peerView: PeerView, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?) + case peer(peerView: PeerView, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool) case replyThread(type: ReplyThreadType, count: Int) case custom(String, String?, Bool) public static func ==(lhs: ChatTitleContent, rhs: ChatTitleContent) -> Bool { switch lhs { - case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount): - if case let .peer(rhsPeerView, rhsCustomTitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount) = rhs { + case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount, isEnabled): + if case let .peer(rhsPeerView, rhsCustomTitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount, rhsIsEnabled) = rhs { if peerView !== rhsPeerView { return false } @@ -58,7 +58,9 @@ public enum ChatTitleContent: Equatable { if customMessageCount != rhsCustomMessageCount { return false } - + if isEnabled != rhsIsEnabled { + return false + } return true } else { return false @@ -169,7 +171,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var titleCredibilityIcon: ChatTitleCredibilityIcon = .none var isEnabled = true switch titleContent { - case let .peer(peerView, customTitle, _, isScheduledMessages, isMuted, _): + case let .peer(peerView, customTitle, _, isScheduledMessages, isMuted, _, isEnabledValue): if peerView.peerId.isReplies { let typeText: String = self.strings.DialogList_Replies segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] @@ -225,6 +227,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } } } + isEnabled = isEnabledValue } case let .replyThread(type, count): let textFont = titleFont @@ -365,7 +368,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var inputActivitiesAllowed = true if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, _, _, isScheduledMessages, _, customMessageCount): + case let .peer(peerView, _, _, isScheduledMessages, _, customMessageCount, _): if let peer = peerViewMainPeer(peerView) { if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies { inputActivitiesAllowed = false @@ -469,7 +472,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } else { if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, _, customMessageCount): + case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _): if let customMessageCount = customMessageCount, customMessageCount != 0 { let string = NSAttributedString(string: self.strings.Conversation_Messages(Int32(customMessageCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) state = .info(string, .generic) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 6e028a3de5..70156804c7 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -2222,7 +2222,7 @@ private final class EmptySearchResultsView: UIView { } public protocol EmojiContentPeekBehavior: AnyObject { - func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, EmojiPagerContentComponent.View.ItemLayer, TelegramMediaFile)?) + func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, CALayer, TelegramMediaFile)?) } public final class EmojiPagerContentComponent: Component { diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index 56ff2958d5..c7cf5a217e 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -2880,7 +2880,12 @@ final class StorageUsageScreenComponent: Component { let totalSize = aggregatedData.selectedSize let _ = (component.context.engine.resources.clearStorage(peerId: component.peer?.id, categories: mappedCategories, includeMessages: aggregatedData.clearIncludeMessages, excludeMessages: aggregatedData.clearExcludeMessages) - |> deliverOnMainQueue).start(completed: { [weak self] in + |> deliverOnMainQueue).start(next: { [weak self] progress in + guard let self else { + return + } + self.updateClearProgress(progress: progress) + }, completed: { [weak self] in guard let self, let _ = self.component else { return } @@ -2921,39 +2926,45 @@ final class StorageUsageScreenComponent: Component { self.isClearing = true self.state?.updated(transition: .immediate) + var totalSize: Int64 = 0 + + let contextStats = aggregatedData.contextStats + + for category in aggregatedData.selectedCategories { + let mappedCategory: StorageUsageStats.CategoryKey + switch category { + case .photos: + mappedCategory = .photos + case .videos: + mappedCategory = .videos + case .files: + mappedCategory = .files + case .music: + mappedCategory = .music + case .other: + continue + case .stickers: + mappedCategory = .stickers + case .avatars: + mappedCategory = .avatars + case .misc: + mappedCategory = .misc + } + + if let value = contextStats.categories[mappedCategory] { + totalSize += value.size + } + } + let _ = (component.context.engine.resources.clearStorage(peerId: component.peer?.id, categories: mappedCategories, includeMessages: [], excludeMessages: []) - |> deliverOnMainQueue).start(completed: { [weak self] in - guard let self, let _ = self.component, let aggregatedData = self.aggregatedData else { + |> deliverOnMainQueue).start(next: { [weak self] progress in + guard let self else { return } - var totalSize: Int64 = 0 - - let contextStats = aggregatedData.contextStats - - for category in aggregatedData.selectedCategories { - let mappedCategory: StorageUsageStats.CategoryKey - switch category { - case .photos: - mappedCategory = .photos - case .videos: - mappedCategory = .videos - case .files: - mappedCategory = .files - case .music: - mappedCategory = .music - case .other: - continue - case .stickers: - mappedCategory = .stickers - case .avatars: - mappedCategory = .avatars - case .misc: - mappedCategory = .misc - } - - if let value = contextStats.categories[mappedCategory] { - totalSize += value.size - } + self.updateClearProgress(progress: progress) + }, completed: { [weak self] in + guard let self else { + return } self.reloadStats(firstTime: false, completion: { [weak self] in @@ -2994,7 +3005,12 @@ final class StorageUsageScreenComponent: Component { } let _ = (component.context.engine.resources.clearStorage(peerIds: aggregatedData.selectionState.selectedPeers, includeMessages: includeMessages, excludeMessages: excludeMessages) - |> deliverOnMainQueue).start(completed: { [weak self] in + |> deliverOnMainQueue).start(next: { [weak self] progress in + guard let self else { + return + } + self.updateClearProgress(progress: progress) + }, completed: { [weak self] in guard let self else { return } @@ -3012,6 +3028,12 @@ final class StorageUsageScreenComponent: Component { } } + private func updateClearProgress(progress: Float) { + if let clearingNode = self.clearingNode { + clearingNode.setProgress(progress) + } + } + private func openKeepMediaCategory(mappedCategory: CacheStorageSettings.PeerStorageCategory, sourceView: StoragePeerTypeItemComponent.View) { guard let component = self.component else { return @@ -3507,8 +3529,8 @@ private class StorageUsageClearProgressOverlayNode: ASDisplayNode { self.addSubnode(self.animationNode) self.addSubnode(self.progressTextNode) self.addSubnode(self.descriptionTextNode) - //self.addSubnode(self.progressBackgroundNode) - //self.addSubnode(self.progressForegroundNode) + self.addSubnode(self.progressBackgroundNode) + self.addSubnode(self.progressForegroundNode) } deinit { @@ -3525,7 +3547,7 @@ private class StorageUsageClearProgressOverlayNode: ASDisplayNode { } private var progress: Float = 0.0 - private func setProgress(_ progress: Float) { + func setProgress(_ progress: Float) { self.progress = progress if let size = self.validLayout { @@ -3562,8 +3584,10 @@ private class StorageUsageClearProgressOverlayNode: ASDisplayNode { self.descriptionTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ClearCache_KeepOpenedDescription, font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor) let descriptionTextSize = self.descriptionTextNode.updateLayout(CGSize(width: size.width - inset * 3.0, height: size.height)) var descriptionTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - descriptionTextSize.width) / 2.0), y: animationFrame.maxY + 52.0), size: descriptionTextSize) + + let progressText: String = "\(Int(self.progress * 100.0))%" - self.progressTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ClearCache_NoProgress, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.presentationData.theme.actionSheet.primaryTextColor) + self.progressTextNode.attributedText = NSAttributedString(string: progressText, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.presentationData.theme.actionSheet.primaryTextColor) let progressTextSize = self.progressTextNode.updateLayout(size) var progressTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - progressTextSize.width) / 2.0), y: descriptionTextFrame.minY - spacing - progressTextSize.height), size: progressTextSize) diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 3a50406c96..e1371e1346 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -483,7 +483,7 @@ private func extractAccountManagerState(records: AccountRecordsView + if peerId == context.account.peerId { + hasPeerInfo = .single(true) + |> then( + hasAvailablePeerInfoMediaPanes(context: context, peerId: peerId) + ) + } else { + hasPeerInfo = .single(true) + } - self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get()) - |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState in + self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get(), hasPeerInfo) + |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState, hasPeerInfo in if let strongSelf = self { var isScheduledMessages = false if case .scheduledMessages = presentationInterfaceState.subject { @@ -4723,7 +4732,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case .pinnedMessages = presentationInterfaceState.subject { strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) } else { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil) + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) let imageOverride: AvatarNodeImageOverride? if strongSelf.context.account.peerId == peer.id { imageOverride = .savedMessagesIcon @@ -5278,7 +5287,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let threadInfo = messageAndTopic.threadData?.info { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount) + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true) let avatarContent: EmojiStatusComponent.Content if strongSelf.chatLocation.threadId == 1 { diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 44d8a0bee3..cf161a5fad 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -801,7 +801,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.emptyNode = emptyNode self.historyNodeContainer.supernode?.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer) if let (size, insets) = self.validEmptyNodeLayout { - emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, emptyType: emptyType, loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate) + emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(emptyType), loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate) } if animated { emptyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -1622,7 +1622,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { emptyNodeInsets.bottom += inputPanelsHeight self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets) if let emptyNode = self.emptyNode, let emptyType = self.emptyType { - emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, emptyType: emptyType, loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition) + emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(emptyType), loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition) transition.updateFrame(node: emptyNode, frame: contentBounds) emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition) } diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 03d0288b4f..7020896e47 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -16,7 +16,7 @@ import ComponentFlow import EmojiStatusComponent private protocol ChatEmptyNodeContent { - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize } private let titleFont = Font.medium(15.0) @@ -36,7 +36,7 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod self.addSubnode(self.textNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -44,12 +44,16 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) let text: String - switch interfaceState.chatLocation { - case .peer, .replyThread, .feed: - if case .scheduledMessages = interfaceState.subject { - text = interfaceState.strings.ScheduledMessages_EmptyPlaceholder - } else { - text = interfaceState.strings.Conversation_EmptyPlaceholder + if case .detailsPlaceholder = subject { + text = interfaceState.strings.ChatList_StartMessaging + } else { + switch interfaceState.chatLocation { + case .peer, .replyThread, .feed: + if case .scheduledMessages = interfaceState.subject { + text = interfaceState.strings.ScheduledMessages_EmptyPlaceholder + } else { + text = interfaceState.strings.Conversation_EmptyPlaceholder + } } } @@ -140,7 +144,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, []) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -309,7 +313,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, []) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -442,7 +446,7 @@ private final class ChatEmptyNodeSecretChatContent: ASDisplayNode, ChatEmptyNode self.addSubnode(self.subtitleNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -576,7 +580,7 @@ private final class ChatEmptyNodeGroupChatContent: ASDisplayNode, ChatEmptyNodeC self.addSubnode(self.subtitleNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -691,7 +695,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC self.addSubnode(self.titleNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -813,7 +817,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, self.addSubnode(self.textNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme @@ -900,6 +904,10 @@ private enum ChatEmptyNodeContentType: Equatable { } final class ChatEmptyNode: ASDisplayNode { + enum Subject { + case emptyChat(ChatHistoryNodeLoadState.EmptyType) + case detailsPlaceholder + } private let context: AccountContext private let interaction: ChatPanelInterfaceInteraction? @@ -953,7 +961,7 @@ final class ChatEmptyNode: ASDisplayNode { } } - func updateLayout(interfaceState: ChatPresentationInterfaceState, emptyType: ChatHistoryNodeLoadState.EmptyType, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { self.wallpaperBackgroundNode = backgroundNode if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { @@ -969,38 +977,43 @@ final class ChatEmptyNode: ASDisplayNode { } let contentType: ChatEmptyNodeContentType - if case .replyThread = interfaceState.chatLocation { - if case .topic = emptyType { - contentType = .topic - } else { - contentType = .regular - } - } else if let peer = interfaceState.renderedPeer?.peer, !isScheduledMessages { - if peer.id == self.context.account.peerId { - contentType = .cloud - } else if let _ = peer as? TelegramSecretChat { - contentType = .secret - } else if let group = peer as? TelegramGroup, case .creator = group.role { - contentType = .group - } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isCreator) && !channel.flags.contains(.isGigagroup) { - contentType = .group - } else if let _ = interfaceState.peerNearbyData { - contentType = .peerNearby - } else if let peer = peer as? TelegramUser { - if peer.isDeleted || peer.botInfo != nil || peer.flags.contains(.isSupport) || peer.isScam || interfaceState.peerIsBlocked { - contentType = .regular - } else if case .clearedHistory = emptyType { - contentType = .regular + switch subject { + case .detailsPlaceholder: + contentType = .regular + case let .emptyChat(emptyType): + if case .replyThread = interfaceState.chatLocation { + if case .topic = emptyType { + contentType = .topic } else { - contentType = .greeting + contentType = .regular + } + } else if let peer = interfaceState.renderedPeer?.peer, !isScheduledMessages { + if peer.id == self.context.account.peerId { + contentType = .cloud + } else if let _ = peer as? TelegramSecretChat { + contentType = .secret + } else if let group = peer as? TelegramGroup, case .creator = group.role { + contentType = .group + } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isCreator) && !channel.flags.contains(.isGigagroup) { + contentType = .group + } else if let _ = interfaceState.peerNearbyData { + contentType = .peerNearby + } else if let peer = peer as? TelegramUser { + if peer.isDeleted || peer.botInfo != nil || peer.flags.contains(.isSupport) || peer.isScam || interfaceState.peerIsBlocked { + contentType = .regular + } else if case .clearedHistory = emptyType { + contentType = .regular + } else { + contentType = .greeting + } + } else { + contentType = .regular } } else { contentType = .regular } - } else { - contentType = .regular } - + var updateGreetingSticker = false var contentTransition = transition if self.content?.0 != contentType { @@ -1044,7 +1057,7 @@ final class ChatEmptyNode: ASDisplayNode { var contentSize = CGSize() if let contentNode = self.content?.1 { - contentSize = contentNode.updateLayout(interfaceState: interfaceState, size: displayRect.size, transition: contentTransition) + contentSize = contentNode.updateLayout(interfaceState: interfaceState, subject: subject, size: displayRect.size, transition: contentTransition) if updateGreetingSticker { self.context.prefetchManager?.prepareNextGreetingSticker() diff --git a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift index e6f5a2760b..8b2f0923a4 100644 --- a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift @@ -1531,7 +1531,7 @@ private class QrContentNode: ASDisplayNode, ContentNode { self.containerNode = ASDisplayNode() - self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground) + self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) self.codeBackgroundNode = ASDisplayNode() self.codeBackgroundNode.backgroundColor = .white @@ -2022,7 +2022,7 @@ private class MessageContentNode: ASDisplayNode, ContentNode { self.containerNode = ASDisplayNode() - self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground) + self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) self.backgroundNode = ASDisplayNode() self.backgroundImageNode = ASImageNode() diff --git a/submodules/TelegramUI/Sources/MediaManager.swift b/submodules/TelegramUI/Sources/MediaManager.swift index 30a4ff7afa..376906d3e6 100644 --- a/submodules/TelegramUI/Sources/MediaManager.swift +++ b/submodules/TelegramUI/Sources/MediaManager.swift @@ -497,11 +497,13 @@ public final class MediaManagerImpl: NSObject, MediaManager { strongSelf.voiceMediaPlayer?.stop() if let (account, playlist, settings, storedState) = inputData { var continueInstantVideoLoopAfterFinish: Bool = true + var controlPlaybackWithProximity: Bool = true if let playlist = playlist as? PeerMessagesMediaPlaylist { continueInstantVideoLoopAfterFinish = playlist.context.sharedContext.energyUsageSettings.autoplayVideo + controlPlaybackWithProximity = playlist.context.sharedContext.currentMediaInputSettings.with({ $0.enableRaiseToSpeak }) } - - let voiceMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: true, type: type, continueInstantVideoLoopAfterFinish: continueInstantVideoLoopAfterFinish) + + let voiceMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: controlPlaybackWithProximity, type: type, continueInstantVideoLoopAfterFinish: continueInstantVideoLoopAfterFinish) strongSelf.voiceMediaPlayer = voiceMediaPlayer voiceMediaPlayer.playedToEnd = { [weak voiceMediaPlayer] in if let strongSelf = self, let voiceMediaPlayer = voiceMediaPlayer, voiceMediaPlayer === strongSelf.voiceMediaPlayer { diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index 19d5617ede..49e8fdfdc4 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -41,8 +41,9 @@ public struct NotificationViewControllerInitializationData { public let encryptionParameters: (Data, Data) public let appVersion: String public let bundleData: Data? + public let useBetaFeatures: Bool - public init(appBundleId: String, appBuildType: TelegramAppBuildType, appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) { + public init(appBundleId: String, appBuildType: TelegramAppBuildType, appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?, useBetaFeatures: Bool) { self.appBundleId = appBundleId self.appBuildType = appBuildType self.appGroupPath = appGroupPath @@ -52,6 +53,7 @@ public struct NotificationViewControllerInitializationData { self.encryptionParameters = encryptionParameters self.appVersion = appVersion self.bundleData = bundleData + self.useBetaFeatures = useBetaFeatures } } @@ -138,7 +140,7 @@ public final class NotificationViewControllerImpl { return nil }) - sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil) + sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil) presentationDataPromise.set(sharedAccountContext!.presentationData) } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index bc975f1429..fe7f59fcae 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -257,6 +257,18 @@ private enum PeerInfoScreenInputData: Equatable { case group(groupId: PeerId) } +public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: PeerId) -> Signal { + let chatLocationContextHolder = Atomic(value: nil) + return peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: .peer(id: peerId), chatLocationContextHolder: chatLocationContextHolder) + |> map { panes -> Bool in + if let panes { + return !panes.isEmpty + } else { + return false + } + } +} + private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic) -> Signal<[PeerInfoPaneKey]?, NoError> { let tags: [(MessageTags, PeerInfoPaneKey)] = [ (.photoOrVideo, .media), diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index 377e0e8401..aa894524a8 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -66,8 +66,9 @@ public struct ShareRootControllerInitializationData { public let encryptionParameters: (Data, Data) public let appVersion: String public let bundleData: Data? + public let useBetaFeatures: Bool - public init(appBundleId: String, appBuildType: TelegramAppBuildType, appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) { + public init(appBundleId: String, appBuildType: TelegramAppBuildType, appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?, useBetaFeatures: Bool) { self.appBundleId = appBundleId self.appBuildType = appBuildType self.appGroupPath = appGroupPath @@ -77,6 +78,7 @@ public struct ShareRootControllerInitializationData { self.encryptionParameters = encryptionParameters self.appVersion = appVersion self.bundleData = bundleData + self.useBetaFeatures = useBetaFeatures } } @@ -239,7 +241,7 @@ public class ShareRootControllerImpl { return nil }) - let sharedContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil) + let sharedContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil) presentationDataPromise.set(sharedContext.presentationData) internalContext = InternalContext(sharedContext: sharedContext) globalInternalContext = internalContext diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 080097b6e4..45a1f1e678 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -15,6 +15,46 @@ import AppBundle import DatePickerNode import DebugSettingsUI import TabBarUI +import WallpaperBackgroundNode +import ChatPresentationInterfaceState + +private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode { + private var presentationData: PresentationData + private var presentationInterfaceState: ChatPresentationInterfaceState + + let wallpaperBackgroundNode: WallpaperBackgroundNode + let emptyNode: ChatEmptyNode + + init(context: AccountContext) { + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), 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.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: true) + self.emptyNode = ChatEmptyNode(context: context, interaction: nil) + + super.init() + + self.addSubnode(self.wallpaperBackgroundNode) + self.addSubnode(self.emptyNode) + } + + func updatePresentationData(_ presentationData: PresentationData) { + self.presentationData = presentationData + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.presentationInterfaceState.limitsConfiguration, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.presentationInterfaceState.accountPeerId, mode: .standard(previewing: false), chatLocation: self.presentationInterfaceState.chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil) + + self.wallpaperBackgroundNode.update(wallpaper: presentationData.chatWallpaper) + } + + func updateLayout(size: CGSize, needsTiling: Bool, transition: ContainedViewLayoutTransition) { + let contentBounds = CGRect(origin: .zero, size: size) + self.wallpaperBackgroundNode.updateLayout(size: size, displayMode: needsTiling ? .aspectFit : .aspectFill, transition: transition) + transition.updateFrame(node: self.wallpaperBackgroundNode, frame: contentBounds) + + self.emptyNode.updateLayout(interfaceState: self.presentationInterfaceState, subject: .detailsPlaceholder, loadingNode: nil, backgroundNode: self.wallpaperBackgroundNode, size: contentBounds.size, insets: .zero, transition: transition) + transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: .zero, size: size)) + self.emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition) + } +} public final class TelegramRootController: NavigationController { private let context: AccountContext @@ -30,6 +70,8 @@ public final class TelegramRootController: NavigationController { private var presentationDataDisposable: Disposable? private var presentationData: PresentationData + private var detailsPlaceholderNode: DetailsChatPlaceholderNode? + private var applicationInFocusDisposable: Disposable? public init(context: AccountContext) { @@ -37,33 +79,13 @@ public final class TelegramRootController: NavigationController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - let navigationDetailsBackgroundMode: NavigationEmptyDetailsBackgoundMode? - switch presentationData.chatWallpaper { - case .color: - let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/EmptyMasterDetailIcon"), color: presentationData.theme.chatList.messageTextColor.withAlphaComponent(0.2)) - navigationDetailsBackgroundMode = image != nil ? .image(image!) : nil - default: - let image = chatControllerBackgroundImage(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper, mediaBox: context.account.postbox.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) - navigationDetailsBackgroundMode = image != nil ? .wallpaper(image!) : nil - } - - super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme), backgroundDetailsMode: navigationDetailsBackgroundMode) + super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme)) self.presentationDataDisposable = (context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { - if presentationData.chatWallpaper != strongSelf.presentationData.chatWallpaper { - let navigationDetailsBackgroundMode: NavigationEmptyDetailsBackgoundMode? - switch presentationData.chatWallpaper { - case .color: - let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/EmptyMasterDetailIcon"), color: presentationData.theme.chatList.messageTextColor.withAlphaComponent(0.2)) - navigationDetailsBackgroundMode = image != nil ? .image(image!) : nil - default: - navigationDetailsBackgroundMode = chatControllerBackgroundImage(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper, mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, knockoutMode: strongSelf.context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper).flatMap(NavigationEmptyDetailsBackgoundMode.wallpaper) - } - strongSelf.updateBackgroundDetailsMode(navigationDetailsBackgroundMode, transition: .immediate) - } - + strongSelf.detailsPlaceholderNode?.updatePresentationData(presentationData) + let previousTheme = strongSelf.presentationData.theme strongSelf.presentationData = presentationData if previousTheme !== presentationData.theme { @@ -92,6 +114,32 @@ public final class TelegramRootController: NavigationController { self.applicationInFocusDisposable?.dispose() } + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let needsRootWallpaperBackgroundNode: Bool + if case .regular = layout.metrics.widthClass { + needsRootWallpaperBackgroundNode = true + } else { + needsRootWallpaperBackgroundNode = false + } + + if needsRootWallpaperBackgroundNode { + let detailsPlaceholderNode: DetailsChatPlaceholderNode + if let current = self.detailsPlaceholderNode { + detailsPlaceholderNode = current + } else { + detailsPlaceholderNode = DetailsChatPlaceholderNode(context: self.context) + detailsPlaceholderNode.wallpaperBackgroundNode.update(wallpaper: self.presentationData.chatWallpaper) + self.detailsPlaceholderNode = detailsPlaceholderNode + } + self.updateDetailsPlaceholderNode(detailsPlaceholderNode) + } else if let _ = self.detailsPlaceholderNode { + self.detailsPlaceholderNode = nil + self.updateDetailsPlaceholderNode(nil) + } + + super.containerLayoutUpdated(layout, transition: transition) + } + public func addRootControllers(showCallsTab: Bool) { let tabBarController = TabBarControllerImpl(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme)) tabBarController.navigationPresentation = .master diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index db1ace0d6f..a2dcaff398 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -40,7 +40,6 @@ public struct ExperimentalUISettings: Codable, Equatable { public var experimentalCompatibility: Bool public var enableDebugDataDisplay: Bool public var acceleratedStickers: Bool - public var experimentalBackground: Bool public var inlineStickers: Bool public var localTranscription: Bool public var enableReactionOverrides: Bool @@ -69,7 +68,6 @@ public struct ExperimentalUISettings: Codable, Equatable { experimentalCompatibility: false, enableDebugDataDisplay: false, acceleratedStickers: false, - experimentalBackground: false, inlineStickers: false, localTranscription: false, enableReactionOverrides: false, @@ -99,7 +97,6 @@ public struct ExperimentalUISettings: Codable, Equatable { experimentalCompatibility: Bool, enableDebugDataDisplay: Bool, acceleratedStickers: Bool, - experimentalBackground: Bool, inlineStickers: Bool, localTranscription: Bool, enableReactionOverrides: Bool, @@ -126,7 +123,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.experimentalCompatibility = experimentalCompatibility self.enableDebugDataDisplay = enableDebugDataDisplay self.acceleratedStickers = acceleratedStickers - self.experimentalBackground = experimentalBackground self.inlineStickers = inlineStickers self.localTranscription = localTranscription self.enableReactionOverrides = enableReactionOverrides @@ -157,7 +153,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.experimentalCompatibility = (try container.decodeIfPresent(Int32.self, forKey: "experimentalCompatibility") ?? 0) != 0 self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0 self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0 - self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0 self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0 self.localTranscription = (try container.decodeIfPresent(Int32.self, forKey: "localTranscription") ?? 0) != 0 self.enableReactionOverrides = try container.decodeIfPresent(Bool.self, forKey: "enableReactionOverrides") ?? false @@ -188,7 +183,6 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encode((self.experimentalCompatibility ? 1 : 0) as Int32, forKey: "experimentalCompatibility") try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay") try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers") - try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground") try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers") try container.encode((self.localTranscription ? 1 : 0) as Int32, forKey: "localTranscription") try container.encode(self.enableReactionOverrides, forKey: "enableReactionOverrides") diff --git a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift index f29c4f68d1..2528a0efd5 100644 --- a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift @@ -282,8 +282,8 @@ public struct EnergyUsageSettings: Codable, Equatable { autoplayVideo: true, autoplayGif: true, loopStickers: true, - loopEmoji: isCapable ? false : true, - fullTranslucency: isCapable ? false : true, + loopEmoji: isCapable, + fullTranslucency: isCapable, extendBackgroundWork: true, autodownloadInBackground: true ) diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index af88317c75..35d5d408ce 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit af88317c7529c5b69c4d574d1293fb385abb3f02 +Subproject commit 35d5d408cee8c69b61a32bc96dbfccc37980a5ae diff --git a/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift index ae77acc904..8b13789179 100644 --- a/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift @@ -1,313 +1 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import GradientBackground -import TelegramPresentationData -import TelegramCore -import AccountContext -import SwiftSignalKit -import WallpaperResources -import FastBlur -import Svg -import GZip -import AppBundle -import AnimatedStickerNode -import TelegramAnimatedStickerNode -import HierarchyTrackingLayer -import MetalKit -import HierarchyTrackingLayer -import simd -private final class NullActionClass: NSObject, CAAction { - static let shared = NullActionClass() - - @objc public func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { - } -} - -@available(iOS 13.0, *) -open class SimpleMetalLayer: CAMetalLayer { - override open func action(forKey event: String) -> CAAction? { - return nullAction - } - - override public init() { - super.init() - } - - override public init(layer: Any) { - super.init(layer: layer) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -private func makePipelineState(device: MTLDevice, library: MTLLibrary, vertexProgram: String, fragmentProgram: String) -> MTLRenderPipelineState? { - guard let loadedVertexProgram = library.makeFunction(name: vertexProgram) else { - return nil - } - guard let loadedFragmentProgram = library.makeFunction(name: fragmentProgram) else { - return nil - } - - let pipelineStateDescriptor = MTLRenderPipelineDescriptor() - pipelineStateDescriptor.vertexFunction = loadedVertexProgram - pipelineStateDescriptor.fragmentFunction = loadedFragmentProgram - pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm - guard let pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineStateDescriptor) else { - return nil - } - - return pipelineState -} - - -@available(iOS 13.0, *) -final class MetalWallpaperBackgroundNode: ASDisplayNode, WallpaperBackgroundNode { - private let device: MTLDevice - private let metalLayer: SimpleMetalLayer - private let commandQueue: MTLCommandQueue - private let renderPipelineState: MTLRenderPipelineState - - private let hierarchyTrackingLayer = HierarchyTrackingLayer() - - var isReady: Signal { - return .single(true) - } - - var rotation: CGFloat = 0.0 - - private var animationPhase: Int = 0 - - private var animationThread: Thread? - private var displayLink: CADisplayLink? - - override init() { - self.device = MTLCreateSystemDefaultDevice()! - self.metalLayer = SimpleMetalLayer() - self.metalLayer.maximumDrawableCount = 3 - self.metalLayer.presentsWithTransaction = true - self.metalLayer.contentsScale = UIScreenScale - self.commandQueue = self.device.makeCommandQueue()! - - let mainBundle = Bundle(for: MetalWallpaperBackgroundNode.self) - - guard let path = mainBundle.path(forResource: "WallpaperBackgroundNodeBundle", ofType: "bundle") else { - preconditionFailure() - } - guard let bundle = Bundle(path: path) else { - preconditionFailure() - } - guard let defaultLibrary = try? self.device.makeDefaultLibrary(bundle: bundle) else { - preconditionFailure() - } - - guard let renderPipelineState = makePipelineState(device: self.device, library: defaultLibrary, vertexProgram: "wallpaperVertex", fragmentProgram: "wallpaperFragment") else { - preconditionFailure() - } - self.renderPipelineState = renderPipelineState - - super.init() - - self.metalLayer.device = self.device - self.metalLayer.pixelFormat = .bgra8Unorm - self.metalLayer.framebufferOnly = true - self.metalLayer.allowsNextDrawableTimeout = true - self.metalLayer.isOpaque = true - - self.layer.addSublayer(self.metalLayer) - self.layer.addSublayer(self.hierarchyTrackingLayer) - - self.hierarchyTrackingLayer.opacity = 0.0 - self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in - self?.updateIsVisible(true) - } - self.hierarchyTrackingLayer.didExitHierarchy = { [weak self] in - self?.updateIsVisible(false) - } - } - - func update(wallpaper: TelegramWallpaper) { - - } - - func _internalUpdateIsSettingUpWallpaper() { - - } - - func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition) { - if self.metalLayer.drawableSize != size { - self.metalLayer.drawableSize = size - - transition.updateFrame(layer: self.metalLayer, frame: CGRect(origin: CGPoint(), size: size)) - - self.redraw() - } - } - - private func updateIsVisible(_ isVisible: Bool) { - if isVisible { - if self.displayLink == nil { - let displayLink = CADisplayLink(target: DisplayLinkTarget { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.redraw() - }, selector: #selector(DisplayLinkTarget.event)) - self.displayLink = displayLink - if #available(iOS 15.0, iOSApplicationExtension 15.0, *) { - if "".isEmpty { - displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 60.0, preferred: 60.0) - } else { - displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond)) - } - } - displayLink.isPaused = false - - if !"".isEmpty { - self.animationThread = Thread(block: { - displayLink.add(to: .current, forMode: .common) - - while true { - if Thread.current.isCancelled { - break - } - RunLoop.current.run(until: .init(timeIntervalSinceNow: 1.0)) - } - }) - self.animationThread?.name = "MetalWallpaperBackgroundNode" - self.animationThread?.qualityOfService = .userInteractive - self.animationThread?.start() - } else { - displayLink.add(to: .current, forMode: .common) - } - } - } else { - if let displayLink = self.displayLink { - self.displayLink = nil - - displayLink.invalidate() - } - if let animationThread = self.animationThread { - self.animationThread = nil - animationThread.cancel() - } - } - } - - private var previousDrawTime: Double? - - private func redraw() { - let timestamp = CACurrentMediaTime() - if let previousDrawTime = self.previousDrawTime { - let _ = previousDrawTime - //print("frame time \((timestamp - previousDrawTime) * 1000.0)") - } - self.previousDrawTime = timestamp - - self.animationPhase += 1 - let animationOffset = Float(self.animationPhase % 200) / 200.0 - let _ = animationOffset - - guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { - return - } - guard let drawable = self.metalLayer.nextDrawable() else { - return - } - - let drawTime = CACurrentMediaTime() - timestamp - if drawTime > 9.0 / 1000.0 { - print("get time \(drawTime * 1000.0)") - } - - let renderPassDescriptor = MTLRenderPassDescriptor() - renderPassDescriptor.colorAttachments[0].texture = drawable.texture - renderPassDescriptor.colorAttachments[0].loadAction = .clear - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor( - red: 0.0, - green: 0.0, - blue: 0.0, - alpha: 1.0 - ) - - guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { - return - } - - var vertices: [Float] = [ - -1.0, -1.0, - 1.0, -1.0, - -1.0, 1.0, - 1.0, 1.0 - ] - - renderEncoder.setRenderPipelineState(self.renderPipelineState) - - renderEncoder.setVertexBytes(&vertices, length: 4 * vertices.count, index: 0) - - var resolution = simd_uint2(UInt32(drawable.texture.width), UInt32(drawable.texture.height)) - renderEncoder.setFragmentBytes(&resolution, length: MemoryLayout.size * 2, index: 0) - - var time = Float(timestamp) * 0.25 - renderEncoder.setFragmentBytes(&time, length: 4, index: 1) - - renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1) - - renderEncoder.endEncoding() - - if self.metalLayer.presentsWithTransaction { - if Thread.isMainThread { - commandBuffer.commit() - commandBuffer.waitUntilScheduled() - drawable.present() - } else { - CATransaction.begin() - commandBuffer.commit() - commandBuffer.waitUntilScheduled() - drawable.present() - CATransaction.commit() - } - } else { - commandBuffer.addScheduledHandler { _ in - drawable.present() - } - commandBuffer.commit() - } - } - - func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) { - - } - - func updateIsLooping(_ isLooping: Bool) { - - } - - func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { - - } - - func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool { - return false - } - - func hasExtraBubbleBackground() -> Bool { - return false - } - - func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? { - return nil - } - - func makeFreeBackground() -> PortalView? { - return nil - } - - func makeDimmedNode() -> ASDisplayNode? { - return nil - } -} diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 0b1d24c63d..9c6f3038b6 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -590,6 +590,53 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } } } + + final class BubbleBackgroundPortalNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode { + private let portalView: PortalView + + var implicitContentUpdate: Bool = true + + init(portalView: PortalView) { + self.portalView = portalView + + super.init() + + self.view.addSubview(portalView.view) + } + + deinit { + } + + func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + if self.portalView.view.bounds.size != rect.size { + transition.updateFrame(view: self.portalView.view, frame: CGRect(origin: CGPoint(), size: rect.size)) + } + } + + func update(rect: CGRect, within containerSize: CGSize, delay: Double = 0.0, transition: ContainedViewLayoutTransition = .immediate) { + if self.portalView.view.bounds.size != rect.size { + transition.updateFrame(view: self.portalView.view, frame: CGRect(origin: CGPoint(), size: rect.size), delay: delay) + } + } + + func update(rect: CGRect, within containerSize: CGSize, animator: ControlledTransitionAnimator) { + if self.portalView.view.bounds.size != rect.size { + animator.updateFrame(layer: self.portalView.view.layer, frame: CGRect(origin: CGPoint(), size: rect.size), completion: nil) + } + } + + func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { + if self.portalView.view.bounds.size != rect.size { + transition.updateFrame(layer: self.portalView.view.layer, frame: CGRect(origin: CGPoint(), size: rect.size)) + } + } + + func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + } + + func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + } + } private final class BubbleBackgroundNodeReference { weak var node: BubbleBackgroundNodeImpl? @@ -603,11 +650,42 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode private let useSharedAnimationPhase: Bool private let contentNode: ASDisplayNode + private var blurredBackgroundContents: UIImage? private var blurredBackgroundPortalSourceView: PortalSourceView? private var blurredBackgroundDimmedNode: GradientBackgroundNode.CloneNode? private var blurredBackgroundDimmedOverlayView: UIView? private var blurredBackgroundContentView: UIImageView? + + private var incomingBackgroundPortalSourceView: PortalSourceView? + private var incomingBackgroundNode: WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl? { + didSet { + if self.incomingBackgroundNode !== oldValue { + if let oldValue { + oldValue.view.removeFromSuperview() + } + if let incomingBackgroundNode = self.incomingBackgroundNode, let incomingBackgroundPortalSourceView = self.incomingBackgroundPortalSourceView { + incomingBackgroundPortalSourceView.addSubview(incomingBackgroundNode.view) + incomingBackgroundNode.frame = CGRect(origin: CGPoint(), size: incomingBackgroundPortalSourceView.bounds.size) + } + } + } + } + + private var outgoingBackgroundPortalSourceView: PortalSourceView? + private var outgoingBackgroundNode: WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl? { + didSet { + if self.outgoingBackgroundNode !== oldValue { + if let oldValue { + oldValue.view.removeFromSuperview() + } + if let outgoingBackgroundNode = self.outgoingBackgroundNode, let outgoingBackgroundPortalSourceView = self.outgoingBackgroundPortalSourceView { + outgoingBackgroundPortalSourceView.addSubview(outgoingBackgroundNode.view) + outgoingBackgroundNode.frame = CGRect(origin: CGPoint(), size: outgoingBackgroundPortalSourceView.bounds.size) + } + } + } + } private var gradientBackgroundNode: GradientBackgroundNode? private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode? @@ -746,6 +824,16 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode let blurredBackgroundContentView = UIImageView() self.blurredBackgroundContentView = blurredBackgroundContentView blurredBackgroundPortalSourceView.addSubview(blurredBackgroundContentView) + + let incomingBackgroundPortalSourceView = PortalSourceView() + self.incomingBackgroundPortalSourceView = incomingBackgroundPortalSourceView + incomingBackgroundPortalSourceView.alpha = 0.00001 + self.view.addSubview(incomingBackgroundPortalSourceView) + + let outgoingBackgroundPortalSourceView = PortalSourceView() + self.outgoingBackgroundPortalSourceView = outgoingBackgroundPortalSourceView + outgoingBackgroundPortalSourceView.alpha = 0.00001 + self.view.addSubview(outgoingBackgroundPortalSourceView) } self.clipsToBounds = true @@ -900,6 +988,18 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.contentNode.isHidden = false } } + + if self.hasBubbleBackground(for: .incoming) { + self.incomingBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .incoming) + } else { + self.incomingBackgroundNode = nil + } + + if self.hasBubbleBackground(for: .outgoing) { + self.outgoingBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .outgoing) + } else { + self.outgoingBackgroundNode = nil + } if let (size, displayMode) = self.validLayout { self.updateLayout(size: size, displayMode: displayMode, transition: .immediate) @@ -1154,6 +1254,14 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode if let blurredBackgroundDimmedOverlayView = self.blurredBackgroundDimmedOverlayView { transition.updateFrame(view: blurredBackgroundDimmedOverlayView, frame: CGRect(origin: CGPoint(), size: size)) } + + if let incomingBackgroundPortalSourceView = self.incomingBackgroundPortalSourceView { + transition.updateFrame(view: incomingBackgroundPortalSourceView, frame: CGRect(origin: CGPoint(), size: size)) + } + + if let outgoingBackgroundPortalSourceView = self.outgoingBackgroundPortalSourceView { + transition.updateFrame(view: outgoingBackgroundPortalSourceView, frame: CGRect(origin: CGPoint(), size: size)) + } transition.updatePosition(node: self.contentNode, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) transition.updateBounds(node: self.contentNode, bounds: CGRect(origin: CGPoint(), size: size)) @@ -1167,6 +1275,16 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode transition.updateFrame(node: outgoingBubbleGradientBackgroundNode, frame: CGRect(origin: CGPoint(), size: size)) outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {}) } + + if let incomingBackgroundNode = self.incomingBackgroundNode { + transition.updateFrame(node: incomingBackgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + incomingBackgroundNode.update(rect: CGRect(origin: CGPoint(), size: size), within: size, transition: transition) + } + + if let outgoingBackgroundNode = self.outgoingBackgroundNode { + transition.updateFrame(node: outgoingBackgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + outgoingBackgroundNode.update(rect: CGRect(origin: CGPoint(), size: size), within: size, transition: transition) + } self.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: transition) @@ -1232,6 +1350,18 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode if let wallpaper = self.wallpaper { self.blurredBackgroundDimmedOverlayView?.backgroundColor = selectDateFillStaticColor(theme: bubbleTheme, wallpaper: wallpaper) } + + if self.hasBubbleBackground(for: .incoming) { + self.incomingBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .incoming) + } else { + self.incomingBackgroundNode = nil + } + + if self.hasBubbleBackground(for: .outgoing) { + self.outgoingBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .outgoing) + } else { + self.outgoingBackgroundNode = nil + } self.updateBubbles() } @@ -1292,9 +1422,31 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode if !self.hasBubbleBackground(for: type) { return nil } + + #if DEBUG && false + var sourceView: PortalSourceView? + switch type { + case .free: + sourceView = self.blurredBackgroundPortalSourceView + case .incoming: + sourceView = self.incomingBackgroundPortalSourceView + case .outgoing: + sourceView = self.outgoingBackgroundPortalSourceView + } + + if let sourceView, let portalView = PortalView(matchPosition: true) { + sourceView.addPortal(view: portalView) + let node = WallpaperBackgroundNodeImpl.BubbleBackgroundPortalNodeImpl(portalView: portalView) + return node + } else { + let node = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type) + return node + } + #else let node = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type) node.updateContents() return node + #endif } func makeFreeBackground() -> PortalView? { @@ -1336,862 +1488,6 @@ private protocol WallpaperComponentView: AnyObject { func update(size: CGSize, transition: ContainedViewLayoutTransition) } -final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroundNode { - final class SharedStorage { - } - - final class BubbleBackgroundNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode { - var implicitContentUpdate = true - - private let bubbleType: WallpaperBubbleType - private let contentNode: ASImageNode - - private var cleanWallpaperNode: ASDisplayNode? - private var gradientWallpaperNode: GradientBackgroundNode.CloneNode? - private weak var backgroundNode: WallpaperBackgroundNodeMergedImpl? - private var index: SparseBag.Index? - - private var currentLayout: (rect: CGRect, containerSize: CGSize)? - - override var frame: CGRect { - didSet { - if oldValue.size != self.bounds.size { - self.contentNode.frame = self.bounds - if let cleanWallpaperNode = self.cleanWallpaperNode { - cleanWallpaperNode.frame = self.bounds - } - if let gradientWallpaperNode = self.gradientWallpaperNode { - gradientWallpaperNode.frame = self.bounds - } - } - } - } - - init(backgroundNode: WallpaperBackgroundNodeMergedImpl, bubbleType: WallpaperBubbleType) { - self.backgroundNode = backgroundNode - self.bubbleType = bubbleType - - self.contentNode = ASImageNode() - self.contentNode.displaysAsynchronously = false - self.contentNode.isUserInteractionEnabled = false - - super.init() - - self.addSubnode(self.contentNode) - - self.index = backgroundNode.bubbleBackgroundNodeReferences.add(BubbleBackgroundNodeReference(node: self)) - } - - deinit { - if let index = self.index, let backgroundNode = self.backgroundNode { - backgroundNode.bubbleBackgroundNodeReferences.remove(index) - } - } - - func updateContents() { - guard let backgroundNode = self.backgroundNode else { - return - } - - if let bubbleTheme = backgroundNode.bubbleTheme, let bubbleCorners = backgroundNode.bubbleCorners { - let wallpaper = backgroundNode.wallpaper ?? bubbleTheme.chat.defaultWallpaper - - let graphics = PresentationResourcesChat.principalGraphics(theme: bubbleTheme, wallpaper: wallpaper, bubbleCorners: bubbleCorners) - var needsCleanBackground = false - switch self.bubbleType { - case .incoming: - self.contentNode.image = graphics.incomingBubbleGradientImage - if graphics.incomingBubbleGradientImage == nil { - self.contentNode.backgroundColor = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill[0] - } else { - self.contentNode.backgroundColor = nil - } - needsCleanBackground = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) - case .outgoing: - if backgroundNode.outgoingBubbleGradientBackgroundNode != nil { - self.contentNode.image = nil - self.contentNode.backgroundColor = nil - } else { - self.contentNode.image = graphics.outgoingBubbleGradientImage - if graphics.outgoingBubbleGradientImage == nil { - self.contentNode.backgroundColor = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill[0] - } else { - self.contentNode.backgroundColor = nil - } - needsCleanBackground = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) - } - case .free: - self.contentNode.image = nil - self.contentNode.backgroundColor = nil - needsCleanBackground = true - } - - var isInvertedGradient = false - var hasComplexGradient = false - switch wallpaper { - case let .file(file): - hasComplexGradient = file.settings.colors.count >= 3 - if let intensity = file.settings.intensity, intensity < 0 { - isInvertedGradient = true - } - case let .gradient(gradient): - hasComplexGradient = gradient.colors.count >= 3 - default: - break - } - - var needsGradientBackground = false - var needsWallpaperBackground = false - - if isInvertedGradient { - switch self.bubbleType { - case .free: - needsCleanBackground = false - case .incoming, .outgoing: - break - } - } - - if needsCleanBackground { - if hasComplexGradient { - needsGradientBackground = backgroundNode.gradient != nil - } else { - needsWallpaperBackground = true - } - } - - var gradientBackgroundSource: GradientBackgroundNode? = backgroundNode.gradient?.gradientBackground - - if case .outgoing = self.bubbleType { - if let outgoingBubbleGradientBackgroundNode = backgroundNode.outgoingBubbleGradientBackgroundNode { - gradientBackgroundSource = outgoingBubbleGradientBackgroundNode - needsWallpaperBackground = false - needsGradientBackground = true - } - } - - if needsWallpaperBackground { - if self.cleanWallpaperNode == nil { - let cleanWallpaperNode = ASImageNode() - cleanWallpaperNode.displaysAsynchronously = false - self.cleanWallpaperNode = cleanWallpaperNode - cleanWallpaperNode.frame = self.bounds - self.insertSubnode(cleanWallpaperNode, at: 0) - } - if let blurredBackgroundContents = backgroundNode.blurredBackgroundContents { - self.cleanWallpaperNode?.contents = blurredBackgroundContents.cgImage - self.cleanWallpaperNode?.backgroundColor = backgroundNode.backgroundColor - } else { - self.cleanWallpaperNode?.contents = nil - self.cleanWallpaperNode?.backgroundColor = backgroundNode.backgroundColor - } - } else { - if let cleanWallpaperNode = self.cleanWallpaperNode { - self.cleanWallpaperNode = nil - cleanWallpaperNode.removeFromSupernode() - } - } - - if needsGradientBackground, let gradientBackgroundNode = gradientBackgroundSource { - if self.gradientWallpaperNode == nil { - let gradientWallpaperNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode) - gradientWallpaperNode.frame = self.bounds - self.gradientWallpaperNode = gradientWallpaperNode - self.insertSubnode(gradientWallpaperNode, at: 0) - } - } else { - if let gradientWallpaperNode = self.gradientWallpaperNode { - self.gradientWallpaperNode = nil - gradientWallpaperNode.removeFromSupernode() - } - } - } else { - self.contentNode.image = nil - if let cleanWallpaperNode = self.cleanWallpaperNode { - self.cleanWallpaperNode = nil - cleanWallpaperNode.removeFromSupernode() - } - } - - if let (rect, containerSize) = self.currentLayout { - self.update(rect: rect, within: containerSize) - } - } - - func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) { - self.update(rect: rect, within: containerSize, delay: 0.0, transition: transition) - } - - func update(rect: CGRect, within containerSize: CGSize, delay: Double, transition: ContainedViewLayoutTransition = .immediate) { - self.currentLayout = (rect, containerSize) - - let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height)) - - transition.updateFrame(layer: self.contentNode.layer, frame: self.bounds) - transition.animateView { - self.contentNode.layer.contentsRect = shiftedContentsRect - } - if let cleanWallpaperNode = self.cleanWallpaperNode { - transition.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds) - transition.animateView { - cleanWallpaperNode.layer.contentsRect = shiftedContentsRect - } - } - if let gradientWallpaperNode = self.gradientWallpaperNode { - transition.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds) - transition.animateView { - gradientWallpaperNode.layer.contentsRect = shiftedContentsRect - } - } - } - - func update(rect: CGRect, within containerSize: CGSize, animator: ControlledTransitionAnimator) { - self.currentLayout = (rect, containerSize) - - let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height)) - - animator.updateFrame(layer: self.contentNode.layer, frame: self.bounds, completion: nil) - animator.updateContentsRect(layer: self.contentNode.layer, contentsRect: shiftedContentsRect, completion: nil) - if let cleanWallpaperNode = self.cleanWallpaperNode { - animator.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds, completion: nil) - animator.updateContentsRect(layer: cleanWallpaperNode.layer, contentsRect: shiftedContentsRect, completion: nil) - } - if let gradientWallpaperNode = self.gradientWallpaperNode { - animator.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds, completion: nil) - animator.updateContentsRect(layer: gradientWallpaperNode.layer, contentsRect: shiftedContentsRect, completion: nil) - } - } - - func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { - self.currentLayout = (rect, containerSize) - - let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height)) - - transition.updateFrame(layer: self.contentNode.layer, frame: self.bounds) - self.contentNode.layer.contentsRect = shiftedContentsRect - if let cleanWallpaperNode = self.cleanWallpaperNode { - transition.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds) - cleanWallpaperNode.layer.contentsRect = shiftedContentsRect - } - if let gradientWallpaperNode = self.gradientWallpaperNode { - transition.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds) - gradientWallpaperNode.layer.contentsRect = shiftedContentsRect - } - } - - func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { - guard let (_, containerSize) = self.currentLayout else { - return - } - let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: animationCurve) - - let scaledOffset = CGPoint(x: value.x / containerSize.width, y: value.y / containerSize.height) - transition.animateContentsRectPositionAdditive(layer: self.contentNode.layer, offset: scaledOffset) - - if let cleanWallpaperNode = self.cleanWallpaperNode { - transition.animateContentsRectPositionAdditive(layer: cleanWallpaperNode.layer, offset: scaledOffset) - } - if let gradientWallpaperNode = self.gradientWallpaperNode { - transition.animateContentsRectPositionAdditive(layer: gradientWallpaperNode.layer, offset: scaledOffset) - } - } - - func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { - guard let (_, containerSize) = self.currentLayout else { - return - } - - let scaledOffset = CGPoint(x: 0.0, y: -value / containerSize.height) - - self.contentNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true) - if let cleanWallpaperNode = self.cleanWallpaperNode { - cleanWallpaperNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true) - } - if let gradientWallpaperNode = self.gradientWallpaperNode { - gradientWallpaperNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true) - } - } - } - - private final class BubbleBackgroundNodeReference { - weak var node: BubbleBackgroundNodeImpl? - - init(node: BubbleBackgroundNodeImpl) { - self.node = node - } - } - - private final class WallpaperGradiendComponentView: WallpaperComponentView { - struct Spec: Equatable { - var colors: [UInt32] - } - - let spec: Spec - let gradientBackground: GradientBackgroundNode - - var view: UIView { - return self.gradientBackground.view - } - - init(spec: Spec, updated: @escaping () -> Void) { - self.spec = spec - - self.gradientBackground = GradientBackgroundNode(colors: spec.colors.map(UIColor.init(rgb:)), useSharedAnimationPhase: true, adjustSaturation: false) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(size: CGSize, transition: ContainedViewLayoutTransition) { - self.gradientBackground.frame = CGRect(origin: CGPoint(), size: size) - self.gradientBackground.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {}) - } - } - - private final class WallpaperColorComponentView: WallpaperComponentView { - struct Spec: Equatable { - var color: UInt32 - } - - let spec: Spec - let backgroundView: UIView - - var view: UIView { - return self.backgroundView - } - - init(spec: Spec, updated: @escaping () -> Void) { - self.spec = spec - - self.backgroundView = UIView() - self.backgroundView.backgroundColor = UIColor(rgb: spec.color) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(size: CGSize, transition: ContainedViewLayoutTransition) { - self.backgroundView.frame = CGRect(origin: CGPoint(), size: size) - } - } - - private final class WallpaperImageComponentView: WallpaperComponentView { - enum Spec: Equatable { - case image( - representation: TelegramMediaImageRepresentation, - isPattern: Bool, - intensity: CGFloat - ) - case builtin - } - - let spec: Spec - let updated: () -> Void - let imageView: UIImageView - var fetchDisposable: Disposable? - var dataDisposable: Disposable? - - var imageData: Data? - - private var validSize: CGSize? - - var view: UIView { - return self.imageView - } - - init(context: AccountContext, spec: Spec, updated: @escaping () -> Void) { - self.spec = spec - self.updated = updated - - self.imageView = UIImageView() - self.imageView.contentMode = .scaleAspectFill - - switch spec { - case let .image(representation, _, _): - self.fetchDisposable = (fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.standalone(resource: representation.resource)) - |> deliverOnMainQueue).start() - self.dataDisposable = (context.account.postbox.mediaBox.resourceData(representation.resource) - |> deliverOnMainQueue).start(next: { [weak self] dataValue in - guard let strongSelf = self else { - return - } - - if dataValue.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: dataValue.path)) { - strongSelf.imageData = data - if let size = strongSelf.validSize { - strongSelf.updateImage(size: size, data: data) - } - } - }) - case .builtin: - if let filePath = getAppBundle().path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg"), let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) { - self.imageData = data - if let size = self.validSize { - self.updateImage(size: size, data: data) - } - } - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.fetchDisposable?.dispose() - self.dataDisposable?.dispose() - } - - func update(size: CGSize, transition: ContainedViewLayoutTransition) { - let sizeUpdated = self.validSize != size - self.validSize = size - - self.imageView.frame = CGRect(origin: CGPoint(), size: size) - - if sizeUpdated || self.imageView.image == nil { - if let imageData = self.imageData { - self.updateImage(size: size, data: imageData) - } - } - } - - private func updateImage(size: CGSize, data: Data) { - let scale: CGFloat - if UIScreenScale >= 2.9 { - scale = 2.5 - } else { - scale = UIScreenScale - } - - switch self.spec { - case let .image(_, isPattern, intensity): - if isPattern { - let patternBackgroundColor: UIColor - let patternForegroundColor: UIColor - if intensity < 0.0 { - patternBackgroundColor = .clear - patternForegroundColor = .black - } else { - patternBackgroundColor = .clear - patternForegroundColor = .black - } - - if let unpackedData = TGGUnzipData(data, 2 * 1024 * 1024), let patternImage = drawSvgImage(unpackedData, CGSize(width: floor(size.width * scale), height: floor(size.height * scale)), patternBackgroundColor, patternForegroundColor, false) { - if intensity < 0.0 { - self.imageView.image = generateImage(patternImage.size, scale: patternImage.scale, rotatedContext: { size, context in - context.setFillColor(UIColor.black.cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) - - if let cgImage = patternImage.cgImage { - context.setBlendMode(.destinationOut) - context.translateBy(x: size.width / 2.0, y: size.height / 2.0) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) - context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size)) - } - }) - self.imageView.alpha = 1.0 - self.imageView.layer.compositingFilter = nil - self.imageView.backgroundColor = UIColor(white: 0.0, alpha: 1.0 - abs(intensity)) - } else { - self.imageView.image = patternImage - self.imageView.alpha = abs(intensity) - self.imageView.layer.compositingFilter = "softLightBlendMode" - self.imageView.backgroundColor = nil - } - } - - self.updated() - } else if let image = UIImage(data: data) { - self.imageView.image = image - self.imageView.layer.compositingFilter = nil - self.imageView.alpha = 1.0 - - self.updated() - } - case .builtin: - if let image = UIImage(data: data) { - self.imageView.image = image - self.imageView.layer.compositingFilter = nil - self.imageView.alpha = 1.0 - - self.updated() - } - } - } - } - - private let context: AccountContext - private let storage: SharedStorage - - private let staticView: UIImageView - private let dynamicView: UIView - private var color: WallpaperColorComponentView? - private var gradient: WallpaperGradiendComponentView? - private var image: WallpaperImageComponentView? - - private var blurredBackgroundContents: UIImage? - - private var isSettingUpWallpaper: Bool = false - - private var wallpaper: TelegramWallpaper? - private var validLayout: CGSize? - - private let _isReady = ValuePromise(false, ignoreRepeated: true) - var isReady: Signal { - return self._isReady.get() - } - - var rotation: CGFloat = 0.0 { - didSet { - } - } - - private var isAnimating: Bool = false - - private var bubbleTheme: PresentationTheme? - private var bubbleCorners: PresentationChatBubbleCorners? - private var bubbleBackgroundNodeReferences = SparseBag() - private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode? - - init(context: AccountContext, storage: SharedStorage?) { - self.context = context - self.storage = storage ?? SharedStorage() - - self.staticView = UIImageView() - self.dynamicView = UIView() - - super.init() - - self.view.addSubview(self.staticView) - } - - func update(wallpaper: TelegramWallpaper) { - self.wallpaper = wallpaper - - var colorSpec: WallpaperColorComponentView.Spec? - var gradientSpec: WallpaperGradiendComponentView.Spec? - var imageSpec: WallpaperImageComponentView.Spec? - - switch wallpaper { - case .builtin: - imageSpec = WallpaperImageComponentView.Spec.builtin - case let .color(color): - colorSpec = WallpaperColorComponentView.Spec(color: color) - case let .gradient(gradient): - if gradient.colors.count >= 3 { - gradientSpec = WallpaperGradiendComponentView.Spec(colors: gradient.colors) - } - case let .image(representations, settings): - if let representation = representations.last { - imageSpec = WallpaperImageComponentView.Spec.image(representation: representation, isPattern: false, intensity: 1.0) - } - let _ = settings - case let .file(file): - if file.settings.colors.count >= 3 { - gradientSpec = WallpaperGradiendComponentView.Spec(colors: file.settings.colors) - } - if let dimensions = file.file.dimensions { - let representation = TelegramMediaImageRepresentation(dimensions: dimensions, resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: file.file.immediateThumbnailData, hasVideo: false, isPersonal: false) - imageSpec = WallpaperImageComponentView.Spec.image(representation: representation, isPattern: file.isPattern, intensity: CGFloat(file.settings.intensity ?? 100) / 100.0) - } - } - - if self.color?.spec != colorSpec { - if let color = self.color { - self.color = nil - color.view.removeFromSuperview() - } - if let colorSpec = colorSpec { - let color = WallpaperColorComponentView(spec: colorSpec, updated: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.componentsUpdated() - }) - self.color = color - if let size = self.validLayout { - color.update(size: size, transition: .immediate) - } - self.dynamicView.insertSubview(color.view, at: 0) - - self.componentsUpdated() - } - } - - if self.gradient?.spec != gradientSpec { - if let gradient = self.gradient { - self.gradient = nil - gradient.view.removeFromSuperview() - } - if let gradientSpec = gradientSpec { - let gradient = WallpaperGradiendComponentView(spec: gradientSpec, updated: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.componentsUpdated() - }) - self.gradient = gradient - if let size = self.validLayout { - gradient.update(size: size, transition: .immediate) - } - self.dynamicView.insertSubview(gradient.view, at: 0) - } - } - - if self.image?.spec != imageSpec { - if let image = self.image { - self.image = nil - image.view.removeFromSuperview() - } - if let imageSpec = imageSpec { - let image = WallpaperImageComponentView(context: self.context, spec: imageSpec, updated: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.componentsUpdated() - }) - self.image = image - if let size = self.validLayout { - image.update(size: size, transition: .immediate) - } - if let gradient = self.gradient { - self.dynamicView.insertSubview(image.view, aboveSubview: gradient.view) - } else { - self.dynamicView.insertSubview(image.view, at: 0) - } - } - } - } - - private func componentsUpdated() { - if self.isAnimating { - if self.dynamicView.superview == nil { - self.view.addSubview(self.dynamicView) - self.staticView.isHidden = true - } - self._isReady.set(true) - } else { - self.staticView.isHidden = false - self.dynamicView.removeFromSuperview() - - if let size = self.validLayout { - if let color = self.color { - self.staticView.image = nil - self.staticView.backgroundColor = color.backgroundView.backgroundColor - } else { - let gradientImage = self.gradient?.gradientBackground.contentView.image - let gradientFrame = self.gradient?.gradientBackground.frame - - let imageImage = self.image?.imageView.image - let imageBackgroundColor = self.image?.imageView.backgroundColor - let imageFrame = self.image?.imageView.frame - let imageAlpha = self.image?.imageView.alpha - let imageFilter = self.image?.imageView.layer.compositingFilter as? String - - self.staticView.image = generateImage(size, opaque: true, scale: nil, rotatedContext: { size, context in - UIGraphicsPushContext(context) - - if let gradientImage = gradientImage, let gradientFrame = gradientFrame { - gradientImage.draw(in: gradientFrame) - } - - if let imageImage = imageImage, let imageFrame = imageFrame, let imageAlpha = imageAlpha { - if imageFilter == "softLightBlendMode" { - context.setBlendMode(.softLight) - } - - if let imageBackgroundColor = imageBackgroundColor { - context.setFillColor(imageBackgroundColor.cgColor) - context.fill(imageFrame) - } - - context.setAlpha(imageAlpha) - - context.translateBy(x: imageFrame.midX, y: imageFrame.midY) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -imageFrame.midX, y: -imageFrame.midY) - if let cgImage = imageImage.cgImage { - let drawingSize = imageImage.size.aspectFilled(imageFrame.size) - context.draw(cgImage, in: CGRect(origin: CGPoint(x: imageFrame.minX + (imageFrame.width - drawingSize.width) / 2.0, y: imageFrame.minX + (imageFrame.height - drawingSize.height) / 2.0), size: drawingSize)) - } - context.translateBy(x: imageFrame.midX, y: imageFrame.midY) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -imageFrame.midX, y: -imageFrame.midY) - - context.setBlendMode(.normal) - context.setAlpha(1.0) - } - - UIGraphicsPopContext() - }) - } - - self._isReady.set(true) - } - } - } - - func _internalUpdateIsSettingUpWallpaper() { - self.isSettingUpWallpaper = true - } - - func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition) { - self.validLayout = size - - self.staticView.frame = CGRect(origin: CGPoint(), size: size) - - if let gradient = self.gradient { - gradient.update(size: size, transition: transition) - } - if let image = self.image { - image.update(size: size, transition: transition) - } - } - - private var isLooping = false - func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) { - if let gradient = self.gradient { - guard !(self.isLooping && self.isAnimating) else { - return - } - self.isAnimating = true - self.componentsUpdated() - gradient.gradientBackground.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.isAnimating = false - if strongSelf.isLooping { - strongSelf.animateEvent(transition: transition, extendAnimation: extendAnimation) - } else { - strongSelf.componentsUpdated() - } - }) - } else { - self.isAnimating = false - } - } - - func updateIsLooping(_ isLooping: Bool) { - let wasLooping = self.isLooping - self.isLooping = isLooping - - if isLooping && !wasLooping { - self.animateEvent(transition: .animated(duration: 0.4, curve: .linear), extendAnimation: false) - } - } - - func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { - if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners { - self.bubbleTheme = bubbleTheme - self.bubbleCorners = bubbleCorners - - if bubbleTheme.chat.message.outgoing.bubble.withoutWallpaper.fill.count >= 3 && bubbleTheme.chat.animateMessageColors { - if self.outgoingBubbleGradientBackgroundNode == nil { - let outgoingBubbleGradientBackgroundNode = GradientBackgroundNode(adjustSaturation: false) - if let size = self.validLayout { - outgoingBubbleGradientBackgroundNode.frame = CGRect(origin: CGPoint(), size: size) - outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {}) - } - self.outgoingBubbleGradientBackgroundNode = outgoingBubbleGradientBackgroundNode - } - self.outgoingBubbleGradientBackgroundNode?.updateColors(colors: bubbleTheme.chat.message.outgoing.bubble.withoutWallpaper.fill) - } else if let _ = self.outgoingBubbleGradientBackgroundNode { - self.outgoingBubbleGradientBackgroundNode = nil - } - - self.updateBubbles() - } - } - - private func updateBubbles() { - for reference in self.bubbleBackgroundNodeReferences { - reference.node?.updateContents() - } - } - - func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool { - guard let bubbleTheme = self.bubbleTheme, let bubbleCorners = self.bubbleCorners else { - return false - } - if self.wallpaper == nil && !self.isSettingUpWallpaper { - return false - } - - var hasPlainWallpaper = false - let graphicsWallpaper: TelegramWallpaper - if let wallpaper = self.wallpaper { - switch wallpaper { - case .color: - hasPlainWallpaper = true - default: - break - } - graphicsWallpaper = wallpaper - } else { - graphicsWallpaper = bubbleTheme.chat.defaultWallpaper - } - - let graphics = PresentationResourcesChat.principalGraphics(theme: bubbleTheme, wallpaper: graphicsWallpaper, bubbleCorners: bubbleCorners) - switch type { - case .incoming: - if graphics.incomingBubbleGradientImage != nil { - return true - } - if bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) { - return !hasPlainWallpaper - } - case .outgoing: - if graphics.outgoingBubbleGradientImage != nil { - return true - } - if bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) { - return !hasPlainWallpaper - } - case .free: - return true - } - - return false - } - - func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? { - if !self.hasBubbleBackground(for: type) { - return nil - } - let node = WallpaperBackgroundNodeMergedImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type) - node.updateContents() - return node - } - - func makeFreeBackground() -> PortalView? { - return nil - } - - func hasExtraBubbleBackground() -> Bool { - return false - } - - func makeDimmedNode() -> ASDisplayNode? { - return nil - } -} - -private let sharedStorage = WallpaperBackgroundNodeMergedImpl.SharedStorage() - -public func createWallpaperBackgroundNode(context: AccountContext, forChatDisplay: Bool, useSharedAnimationPhase: Bool = false, useExperimentalImplementation: Bool = false) -> WallpaperBackgroundNode { - if forChatDisplay && useExperimentalImplementation { - #if DEBUG - if #available(iOS 13.0, iOSApplicationExtension 13.0, *) { - return MetalWallpaperBackgroundNode() - } - #else - return WallpaperBackgroundNodeMergedImpl(context: context, storage: useSharedAnimationPhase ? sharedStorage : nil) - #endif - } - +public func createWallpaperBackgroundNode(context: AccountContext, forChatDisplay: Bool, useSharedAnimationPhase: Bool = false) -> WallpaperBackgroundNode { return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase) }