diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 44d3bf0aa7..3312c333c5 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -718,7 +718,12 @@ private final class NotificationServiceHandler { let _ = (combineLatest(queue: self.queue, self.accountManager.accountRecords(), - self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings, ApplicationSpecificSharedDataKeys.voiceCallSettings, SharedDataKeys.loggingSettings]) + self.accountManager.sharedData(keys: [ + ApplicationSpecificSharedDataKeys.inAppNotificationSettings, + ApplicationSpecificSharedDataKeys.voiceCallSettings, + ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings, + SharedDataKeys.loggingSettings + ]) ) |> take(1) |> deliverOn(self.queue)).start(next: { [weak self] records, sharedData in @@ -728,6 +733,14 @@ private final class NotificationServiceHandler { let loggingSettings = sharedData.entries[SharedDataKeys.loggingSettings]?.get(LoggingSettings.self) ?? LoggingSettings.defaultSettings Logger.shared.logToFile = loggingSettings.logToFile Logger.shared.logToConsole = loggingSettings.logToConsole + + var automaticMediaDownloadSettings: MediaAutoDownloadSettings + if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings]?.get(MediaAutoDownloadSettings.self) { + automaticMediaDownloadSettings = value + } else { + automaticMediaDownloadSettings = MediaAutoDownloadSettings.defaultSettings + } + let shouldSynchronizeState = automaticMediaDownloadSettings.energyUsageSettings.synchronizeInBackground if let keyId = notificationPayloadKeyId(data: payloadData) { outer: for listRecord in records.records { @@ -1406,34 +1419,38 @@ private final class NotificationServiceHandler { let pollSignal: Signal - stateManager.network.shouldKeepConnection.set(.single(true)) - if peerId.namespace == Namespaces.Peer.CloudChannel { - Logger.shared.log("NotificationService \(episode)", "Will poll channel \(peerId)") - - pollSignal = standalonePollChannelOnce( - accountPeerId: stateManager.accountPeerId, - postbox: stateManager.postbox, - network: stateManager.network, - peerId: peerId, - stateManager: stateManager - ) + if !shouldSynchronizeState { + pollSignal = .complete() } else { - Logger.shared.log("NotificationService \(episode)", "Will perform non-specific getDifference") - enum ControlError { - case restart - } - let signal = stateManager.standalonePollDifference() - |> castError(ControlError.self) - |> mapToSignal { result -> Signal in - if result { - return .complete() - } else { - return .fail(.restart) + stateManager.network.shouldKeepConnection.set(.single(true)) + if peerId.namespace == Namespaces.Peer.CloudChannel { + Logger.shared.log("NotificationService \(episode)", "Will poll channel \(peerId)") + + pollSignal = standalonePollChannelOnce( + accountPeerId: stateManager.accountPeerId, + postbox: stateManager.postbox, + network: stateManager.network, + peerId: peerId, + stateManager: stateManager + ) + } else { + Logger.shared.log("NotificationService \(episode)", "Will perform non-specific getDifference") + enum ControlError { + case restart } + let signal = stateManager.standalonePollDifference() + |> castError(ControlError.self) + |> mapToSignal { result -> Signal in + if result { + return .complete() + } else { + return .fail(.restart) + } + } + |> restartIfError + + pollSignal = signal } - |> restartIfError - - pollSignal = signal } let pollWithUpdatedContent: Signal diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 42a6cd51e4..6488380fee 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -751,6 +751,8 @@ public protocol SharedAccountContext: AnyObject { var currentInAppNotificationSettings: Atomic { get } var currentMediaInputSettings: Atomic { get } + var energyUsageSettings: EnergyUsageSettings { get } + var applicationBindings: TelegramApplicationBindings { get } var authorizationPushConfiguration: Signal { get } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 033a104006..5323d57412 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1754,12 +1754,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController Queue.mainQueue().after(1.0, { let _ = ( - self.context.engine.data.get(TelegramEngine.EngineData.Item.Notices.Notice(key: ApplicationSpecificNotice.forcedPasswordSetupKey())) - |> map { entry -> Int32? in - return entry?.get(ApplicationSpecificCounterNotice.self)?.value + self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId), + TelegramEngine.EngineData.Item.Notices.Notice(key: ApplicationSpecificNotice.forcedPasswordSetupKey()) + ) + |> map { peer, entry -> (phoneNumber: String?, nortice: Int32?) in + var phoneNumber: String? + if case let .user(user) = peer { + phoneNumber = user.phone + } + return (phoneNumber, entry?.get(ApplicationSpecificCounterNotice.self)?.value) } |> deliverOnMainQueue - ).start(next: { [weak self] value in + ).start(next: { [weak self] phoneNumber, value in guard let strongSelf = self else { return } @@ -1772,7 +1779,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController title: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_Title, text: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_Text, actionText: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_Action, - doneText: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_DoneAction + doneText: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_DoneAction, + phoneNumber: phoneNumber ))) controller.dismissConfirmation = { [weak controller] f in guard let strongSelf = self, let controller = controller else { diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index b9a3938c50..2d91aad817 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -13,6 +13,7 @@ import SearchUI import ContextUI import AnimationCache import MultiAnimationRenderer +import TelegramUIPreferences enum ChatListContainerNodeFilter: Equatable { case all @@ -647,13 +648,26 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { return (state, filterId) }) + let enablePreload = context.sharedContext.accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings])) + |> map { sharedData -> Bool in + var automaticMediaDownloadSettings: MediaAutoDownloadSettings + if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings]?.get(MediaAutoDownloadSettings.self) { + automaticMediaDownloadSettings = value + } else { + automaticMediaDownloadSettings = MediaAutoDownloadSettings.defaultSettings + } + return automaticMediaDownloadSettings.energyUsageSettings.autodownloadInBackground + } + |> distinctUntilChanged + if self.controlsHistoryPreload, case .chatList(groupId: .root) = self.location { self.context.account.viewTracker.chatListPreloadItems.set(combineLatest(queue: .mainQueue(), context.sharedContext.hasOngoingCall.get(), - itemNode.listNode.preloadItems.get() + itemNode.listNode.preloadItems.get(), + enablePreload ) - |> map { hasOngoingCall, preloadItems -> Set in - if hasOngoingCall { + |> map { hasOngoingCall, preloadItems, enablePreload -> Set in + if hasOngoingCall || !enablePreload { return Set() } else { return Set(preloadItems) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 6be7db083f..392c8f668d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -675,7 +675,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { animationCache: context.animationCache, animationRenderer: context.animationRenderer, content: titleTopicIconContent, - isVisibleForAnimations: currentNode?.visibilityStatus ?? false, + isVisibleForAnimations: (currentNode?.visibilityStatus ?? false) && context.sharedContext.energyUsageSettings.loopEmoji, action: nil ) @@ -2788,7 +2788,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { animationCache: item.interaction.animationCache, animationRenderer: item.interaction.animationRenderer, content: avatarIconContent, - isVisibleForAnimations: strongSelf.visibilityStatus, + isVisibleForAnimations: strongSelf.visibilityStatus && item.context.sharedContext.energyUsageSettings.loopEmoji, action: nil ) strongSelf.avatarIconComponent = avatarIconComponent @@ -3285,7 +3285,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { animationCache: item.interaction.animationCache, animationRenderer: item.interaction.animationRenderer, content: currentCredibilityIconContent, - isVisibleForAnimations: strongSelf.visibilityStatus, + isVisibleForAnimations: strongSelf.visibilityStatus && item.context.sharedContext.energyUsageSettings.loopEmoji, action: nil ) strongSelf.credibilityIconComponent = credibilityIconComponent diff --git a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift index 35d2f8881a..3fdc76cb2a 100644 --- a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift +++ b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift @@ -170,7 +170,7 @@ public final class ReactionIconView: PortalSourceView { animationLayer.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize) - animationLayer.isVisibleForAnimations = animateIdle + animationLayer.isVisibleForAnimations = animateIdle && context.sharedContext.energyUsageSettings.loopEmoji } func reset() { diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift index 5a23069496..bed5cfbedc 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift @@ -35,7 +35,7 @@ public enum TwoFactorDataInputMode { case authorized } - case password(doneText: String) + case password(phoneNumber: String?, doneText: String) case passwordRecoveryEmail(emailPattern: String, mode: PasswordRecoveryEmailMode, doneText: String) case passwordRecovery(recovery: Recovery, doneText: String) case emailAddress(password: String, hint: String, doneText: String) @@ -99,8 +99,11 @@ public final class TwoFactorDataInputScreen: ViewController { public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - if case .rememberPassword = self.mode { + switch self.mode { + case .rememberPassword, .password: (self.displayNode as? TwoFactorDataInputScreenNode)?.focus() + default: + break } } @@ -110,7 +113,7 @@ public final class TwoFactorDataInputScreen: ViewController { return } switch strongSelf.mode { - case let .password(doneText): + case let .password(_, doneText): let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText if values.count != 2 { return @@ -924,7 +927,7 @@ public final class TwoFactorDataInputScreen: ViewController { } private enum TwoFactorDataInputTextNodeType { - case password(confirmation: Bool) + case password(phoneNumber: String?, confirmation: Bool) case email case code case hint @@ -984,6 +987,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega private let toggleTextHidden: (TwoFactorDataInputTextNode) -> Void private let backgroundNode: ASImageNode + private var shadowInputNode: TextFieldNode? private let inputNode: TextFieldNode private let hideButtonNode: HighlightableButtonNode private let clearButtonNode: HighlightableButtonNode @@ -1068,7 +1072,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega self.hideButtonNode = HighlightableButtonNode() switch mode { - case let .password(confirmation): + case let .password(phoneNumber, confirmation): self.inputNode.textField.keyboardType = .default self.inputNode.textField.isSecureTextEntry = true if confirmation { @@ -1076,29 +1080,68 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega } else { self.inputNode.textField.returnKeyType = .next } + if #available(iOS 12.0, *) { + if !confirmation, let phoneNumber { + let shadowInputNode = TextFieldNode() + shadowInputNode.textField.font = Font.regular(17.0) + shadowInputNode.textField.textColor = theme.list.freePlainInputField.primaryColor + shadowInputNode.textField.attributedPlaceholder = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: theme.list.freePlainInputField.placeholderColor) + self.shadowInputNode = shadowInputNode + + + shadowInputNode.textField.textContentType = .username + shadowInputNode.textField.text = phoneNumber + } + + self.inputNode.textField.textContentType = .newPassword + self.inputNode.textField.passwordRules = UITextInputPasswordRules(descriptor: "minlength: 8;") + } self.hideButtonNode.isHidden = confirmation case .email: self.inputNode.textField.keyboardType = .emailAddress self.inputNode.textField.returnKeyType = .done self.hideButtonNode.isHidden = true + + if #available(iOS 12.0, *) { + self.inputNode.textField.textContentType = .emailAddress + } + + self.inputNode.textField.autocorrectionType = .no + self.inputNode.textField.autocapitalizationType = .none + self.inputNode.textField.spellCheckingType = .no + if #available(iOS 11.0, *) { + self.inputNode.textField.smartQuotesType = .no + self.inputNode.textField.smartDashesType = .no + self.inputNode.textField.smartInsertDeleteType = .no + } case .code: self.inputNode.textField.keyboardType = .numberPad self.inputNode.textField.returnKeyType = .done self.hideButtonNode.isHidden = true + + self.inputNode.textField.autocorrectionType = .no + self.inputNode.textField.autocapitalizationType = .none + self.inputNode.textField.spellCheckingType = .no + if #available(iOS 11.0, *) { + self.inputNode.textField.smartQuotesType = .no + self.inputNode.textField.smartDashesType = .no + self.inputNode.textField.smartInsertDeleteType = .no + } case .hint: self.inputNode.textField.keyboardType = .asciiCapable self.inputNode.textField.returnKeyType = .done self.hideButtonNode.isHidden = true + + self.inputNode.textField.autocorrectionType = .no + self.inputNode.textField.autocapitalizationType = .none + self.inputNode.textField.spellCheckingType = .no + if #available(iOS 11.0, *) { + self.inputNode.textField.smartQuotesType = .no + self.inputNode.textField.smartDashesType = .no + self.inputNode.textField.smartInsertDeleteType = .no + } } - self.inputNode.textField.autocorrectionType = .no - self.inputNode.textField.autocapitalizationType = .none - self.inputNode.textField.spellCheckingType = .no - if #available(iOS 11.0, *) { - self.inputNode.textField.smartQuotesType = .no - self.inputNode.textField.smartDashesType = .no - self.inputNode.textField.smartInsertDeleteType = .no - } self.inputNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance self.hideButtonNode.setImage(generateTextHiddenImage(color: theme.list.freePlainInputField.controlColor, on: false), for: []) @@ -1110,6 +1153,10 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega super.init() self.addSubnode(self.backgroundNode) + if let shadowInputNode = self.shadowInputNode { + shadowInputNode.alpha = 0.001 + self.addSubnode(shadowInputNode) + } self.addSubnode(self.inputNode) self.addSubnode(self.hideButtonNode) @@ -1179,6 +1226,10 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega let leftInset: CGFloat = 16.0 let rightInset: CGFloat = 38.0 + if let shadowInputNode = self.shadowInputNode { + transition.updateFrame(node: shadowInputNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height))) + } + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.inputNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height))) transition.updateFrame(node: self.hideButtonNode, frame: CGRect(origin: CGPoint(x: size.width - rightInset - 4.0, y: 0.0), size: CGSize(width: rightInset + 4.0, height: size.height))) @@ -1299,7 +1350,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS var toggleTextHidden: ((TwoFactorDataInputTextNode) -> Void)? switch mode { - case .password: + case let .password(phoneNumber, _): title = presentationData.strings.TwoFactorSetup_Password_Title text = NSAttributedString(string: "", font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor) buttonText = presentationData.strings.TwoFactorSetup_Password_Action @@ -1307,7 +1358,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS changeEmailActionText = "" resendCodeActionText = "" inputNodes = [ - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: false), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderPassword, focusUpdated: { node, focused in + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: phoneNumber, confirmation: false), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderPassword, focusUpdated: { node, focused in focusUpdated?(node, focused) }, next: { node in next?(node) @@ -1316,7 +1367,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS }, toggleTextHidden: { node in toggleTextHidden?(node) }), - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: true), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderConfirmPassword, focusUpdated: { node, focused in + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: phoneNumber, confirmation: true), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderConfirmPassword, focusUpdated: { node, focused in focusUpdated?(node, focused) }, next: { node in next?(node) @@ -1334,7 +1385,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS changeEmailActionText = "" resendCodeActionText = "" inputNodes = [ - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: false), placeholder: presentationData.strings.TwoFactorSetup_PasswordRecovery_PlaceholderPassword, focusUpdated: { node, focused in + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: nil, confirmation: false), placeholder: presentationData.strings.TwoFactorSetup_PasswordRecovery_PlaceholderPassword, focusUpdated: { node, focused in focusUpdated?(node, focused) }, next: { node in next?(node) @@ -1343,7 +1394,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS }, toggleTextHidden: { node in toggleTextHidden?(node) }), - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: true), placeholder: presentationData.strings.TwoFactorSetup_PasswordRecovery_PlaceholderConfirmPassword, focusUpdated: { node, focused in + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: nil, confirmation: true), placeholder: presentationData.strings.TwoFactorSetup_PasswordRecovery_PlaceholderConfirmPassword, focusUpdated: { node, focused in focusUpdated?(node, focused) }, next: { node in next?(node) @@ -1453,7 +1504,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS changeEmailActionText = "" resendCodeActionText = "" inputNodes = [ - TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(confirmation: false), placeholder: presentationData.strings.TwoFactorRemember_Placeholder, focusUpdated: { node, focused in + TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: nil, confirmation: false), placeholder: presentationData.strings.TwoFactorRemember_Placeholder, focusUpdated: { node, focused in focusUpdated?(node, focused) }, next: { node in next?(node) diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift index 5d2912da32..acb4735354 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift @@ -18,17 +18,20 @@ public enum TwoFactorAuthSplashMode { public var text: String public var actionText: String public var doneText: String + public var phoneNumber: String? public init( title: String, text: String, actionText: String, - doneText: String + doneText: String, + phoneNumber: String? ) { self.title = title self.text = text self.actionText = actionText self.doneText = doneText + self.phoneNumber = phoneNumber } } @@ -100,7 +103,7 @@ public final class TwoFactorAuthSplashScreen: ViewController { } switch strongSelf.mode { case let .intro(intro): - strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password(doneText: intro.doneText), stateUpdated: { _ in + strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .password(phoneNumber: intro.phoneNumber, doneText: intro.doneText), stateUpdated: { _ in }, presentation: strongSelf.navigationPresentation)) case .done, .remember: guard let navigationController = strongSelf.navigationController as? NavigationController else { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 20f0abd45f..f2e7a8904c 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -735,9 +735,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } - #if DEBUG - loopIdle = true - #endif + if !self.context.sharedContext.energyUsageSettings.loopEmoji { + loopIdle = false + } var validIndices = Set() var nextX: CGFloat = sideInset diff --git a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift index 5e6d8a8cee..472193a55c 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift @@ -33,12 +33,13 @@ private final class DataAndStorageControllerArguments { let toggleRaiseToListen: (Bool) -> Void let toggleAutoplayGifs: (Bool) -> Void let toggleAutoplayVideos: (Bool) -> Void + let openEnergySavingSettings: () -> Void let toggleDownloadInBackground: (Bool) -> Void let openBrowserSelection: () -> Void let openIntents: () -> Void let toggleEnableSensitiveContent: (Bool) -> Void - init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, toggleVoiceUseLessData: @escaping (Bool) -> Void, openSaveIncoming: @escaping (AutomaticSaveIncomingPeerType) -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, togglePauseMusicOnRecording: @escaping (Bool) -> Void, toggleRaiseToListen: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) { + init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, toggleVoiceUseLessData: @escaping (Bool) -> Void, openSaveIncoming: @escaping (AutomaticSaveIncomingPeerType) -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, togglePauseMusicOnRecording: @escaping (Bool) -> Void, toggleRaiseToListen: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, openEnergySavingSettings: @escaping () -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) { self.openStorageUsage = openStorageUsage self.openNetworkUsage = openNetworkUsage self.openProxy = openProxy @@ -51,6 +52,7 @@ private final class DataAndStorageControllerArguments { self.toggleRaiseToListen = toggleRaiseToListen self.toggleAutoplayGifs = toggleAutoplayGifs self.toggleAutoplayVideos = toggleAutoplayVideos + self.openEnergySavingSettings = openEnergySavingSettings self.toggleDownloadInBackground = toggleDownloadInBackground self.openBrowserSelection = openBrowserSelection self.openIntents = openIntents @@ -64,6 +66,7 @@ private enum DataAndStorageSection: Int32 { case autoSave case backgroundDownload case autoPlay + case energySaving case voiceCalls case other case connection @@ -107,6 +110,9 @@ private enum DataAndStorageEntry: ItemListNodeEntry { case autoplayHeader(PresentationTheme, String) case autoplayGifs(PresentationTheme, String, Bool) case autoplayVideos(PresentationTheme, String, Bool) + + case energySaving + case useLessVoiceData(PresentationTheme, String, Bool) case useLessVoiceDataInfo(PresentationTheme, String) case otherHeader(PresentationTheme, String) @@ -135,6 +141,8 @@ private enum DataAndStorageEntry: ItemListNodeEntry { return DataAndStorageSection.voiceCalls.rawValue case .autoplayHeader, .autoplayGifs, .autoplayVideos: return DataAndStorageSection.autoPlay.rawValue + case .energySaving: + return DataAndStorageSection.energySaving.rawValue case .otherHeader, .shareSheet, .saveEditedPhotos, .openLinksIn, .pauseMusicOnRecording, .raiseToListen, .raiseToListenInfo: return DataAndStorageSection.other.rawValue case .connectionHeader, .connectionProxy: @@ -178,10 +186,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry { return 26 case .autoplayVideos: return 27 - case .otherHeader: + case .energySaving: return 28 - case .shareSheet: + case .otherHeader: return 29 + case .shareSheet: + return 30 case .saveEditedPhotos: return 31 case .openLinksIn: @@ -275,6 +285,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry { } else { return false } + case .energySaving: + if case .energySaving = rhs { + return true + } else { + return false + } case let .useLessVoiceData(lhsTheme, lhsText, lhsValue): if case let .useLessVoiceData(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true @@ -424,6 +440,11 @@ private enum DataAndStorageEntry: ItemListNodeEntry { return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleAutoplayVideos(value) }, tag: DataAndStorageEntryTag.autoplayVideos) + case .energySaving: + //TODO:localize + return ItemListDisclosureItem(presentationData: presentationData, title: "Energy Settings", label: "", sectionId: self.section, style: .blocks, action: { + arguments.openEnergySavingSettings() + }) case let .useLessVoiceData(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleVoiceUseLessData(value) @@ -667,6 +688,8 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat entries.append(.autoplayGifs(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayGifs, data.automaticMediaDownloadSettings.autoplayGifs)) entries.append(.autoplayVideos(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayVideos, data.automaticMediaDownloadSettings.autoplayVideos)) + entries.append(.energySaving) + entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other)) if #available(iOSApplicationExtension 13.2, iOS 13.2, *) { entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings)) @@ -922,6 +945,8 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da settings.autoplayVideos = value return settings }).start() + }, openEnergySavingSettings: { + pushControllerImpl?(energySavingSettingsScreen(context: context)) }, toggleDownloadInBackground: { value in let _ = updateMediaDownloadSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in var settings = settings diff --git a/submodules/SettingsUI/Sources/Data and Storage/EnergySavingSettingsScreen.swift b/submodules/SettingsUI/Sources/Data and Storage/EnergySavingSettingsScreen.swift new file mode 100644 index 0000000000..f8be70bf3f --- /dev/null +++ b/submodules/SettingsUI/Sources/Data and Storage/EnergySavingSettingsScreen.swift @@ -0,0 +1,199 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import ItemListUI +import PresentationDataUtils +import AccountContext + +enum ItemType: CaseIterable { + case loopEmoji + case playVideoAvatars + case fullTranslucency + case extendBackgroundWork + case synchronizeInBackground + case autodownloadInBackground + + var settingsKeyPath: WritableKeyPath { + switch self { + case .loopEmoji: + return \.loopEmoji + case .playVideoAvatars: + return \.playVideoAvatars + case .fullTranslucency: + return \.fullTranslucency + case .extendBackgroundWork: + return \.extendBackgroundWork + case .synchronizeInBackground: + return \.synchronizeInBackground + case .autodownloadInBackground: + return \.autodownloadInBackground + } + } + + func title(strings: PresentationStrings) -> String { + //TODO:localize + switch self { + case .loopEmoji: + return "Loop Animated Emoji" + case .playVideoAvatars: + return "Play Video Avatars" + case .fullTranslucency: + return "Translucency Effects" + case .extendBackgroundWork: + return "Extended Background Time" + case .synchronizeInBackground: + return "Background Sync" + case .autodownloadInBackground: + return "Preload Media in Chats" + } + } +} + +private final class EnergeSavingSettingsScreenArguments { + let toggleAll: (Bool) -> Void + let toggleItem: (ItemType) -> Void + + init(toggleAll: @escaping (Bool) -> Void, toggleItem: @escaping (ItemType) -> Void) { + self.toggleAll = toggleAll + self.toggleItem = toggleItem + } +} + +private enum EnergeSavingSettingsScreenSection: Int32 { + case all + case items +} + +private enum EnergeSavingSettingsScreenEntry: ItemListNodeEntry { + enum StableId: Hashable { + case all + case item(ItemType) + } + + case all(Bool) + case item(index: Int, type: ItemType, value: Bool) + + var section: ItemListSectionId { + switch self { + case .all: + return EnergeSavingSettingsScreenSection.all.rawValue + case .item: + return EnergeSavingSettingsScreenSection.items.rawValue + } + } + + var sortIndex: Int { + switch self { + case .all: + return -1 + case let .item(index, _, _): + return index + } + } + + var stableId: StableId { + switch self { + case .all: + return .all + case let .item(_, type, _): + return .item(type) + } + } + + static func <(lhs: EnergeSavingSettingsScreenEntry, rhs: EnergeSavingSettingsScreenEntry) -> Bool { + return lhs.sortIndex < rhs.sortIndex + } + + func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { + let arguments = arguments as! EnergeSavingSettingsScreenArguments + switch self { + case let .all(value): + //TODO:localize + return ItemListSwitchItem(presentationData: presentationData, title: "Enable All", value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + arguments.toggleAll(value) + }) + case let .item(_, type, value): + return ItemListSwitchItem(presentationData: presentationData, title: type.title(strings: presentationData.strings), value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + arguments.toggleItem(type) + }) + } + } +} + +private func energeSavingSettingsScreenEntries( + presentationData: PresentationData, + settings: MediaAutoDownloadSettings +) -> [EnergeSavingSettingsScreenEntry] { + var entries: [EnergeSavingSettingsScreenEntry] = [] + + entries.append(.all(ItemType.allCases.allSatisfy({ item in settings.energyUsageSettings[keyPath: item.settingsKeyPath] }))) + + for type in ItemType.allCases { + entries.append(.item(index: entries.count, type: type, value: settings.energyUsageSettings[keyPath: type.settingsKeyPath])) + } + + return entries +} + +func energySavingSettingsScreen(context: AccountContext) -> ViewController { + var pushControllerImpl: ((ViewController) -> Void)? + let _ = pushControllerImpl + + let arguments = EnergeSavingSettingsScreenArguments( + toggleAll: { value in + let _ = updateMediaDownloadSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in + var settings = settings + for type in ItemType.allCases { + settings.energyUsageSettings[keyPath: type.settingsKeyPath] = value + } + return settings + }).start() + }, + toggleItem: { type in + let _ = updateMediaDownloadSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in + var settings = settings + settings.energyUsageSettings[keyPath: type.settingsKeyPath] = !settings.energyUsageSettings[keyPath: type.settingsKeyPath] + return settings + }).start() + } + ) + + let signal = combineLatest( + context.sharedContext.presentationData, + context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings])) + |> deliverOnMainQueue + |> map { presentationData, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in + var automaticMediaDownloadSettings: MediaAutoDownloadSettings + if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings]?.get(MediaAutoDownloadSettings.self) { + automaticMediaDownloadSettings = value + } else { + automaticMediaDownloadSettings = MediaAutoDownloadSettings.defaultSettings + } + + //TODO:localize + let controllerState = ItemListControllerState( + presentationData: ItemListPresentationData(presentationData), + title: .text("Energy Saving"), + leftNavigationButton: nil, + rightNavigationButton: nil, + backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), + animateChanges: false + ) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: energeSavingSettingsScreenEntries(presentationData: presentationData, settings: automaticMediaDownloadSettings), style: .blocks, emptyStateItem: nil, animateChanges: true) + + return (controllerState, (listState, arguments)) + } + + let controller = ItemListController(context: context, state: signal) + pushControllerImpl = { [weak controller] c in + if let controller = controller { + (controller.navigationController as? NavigationController)?.pushViewController(c) + } + } + return controller +} diff --git a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift index 51c95be1a2..cf57cc61cf 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift @@ -248,7 +248,8 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo title: presentationData.strings.TwoFactorSetup_Intro_Title, text: presentationData.strings.TwoFactorSetup_Intro_Text, actionText: presentationData.strings.TwoFactorSetup_Intro_Action, - doneText: presentationData.strings.TwoFactorSetup_Done_Action + doneText: presentationData.strings.TwoFactorSetup_Done_Action, + phoneNumber: nil ))) replaceTopControllerImpl?(controller, false) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index d204760334..efa3ed0b0e 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -944,28 +944,37 @@ public func privacyAndSecurityController( } }) }, openTwoStepVerification: { data in - if let data = data { - switch data { - case .set: - break - case let .notSet(pendingEmail): - if pendingEmail == nil { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = TwoFactorAuthSplashScreen(sharedContext: context.sharedContext, engine: .authorized(context.engine), mode: .intro(.init( - title: presentationData.strings.TwoFactorSetup_Intro_Title, - text: presentationData.strings.TwoFactorSetup_Intro_Text, - actionText: presentationData.strings.TwoFactorSetup_Intro_Action, - doneText: presentationData.strings.TwoFactorSetup_Done_Action - ))) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let data = data { + switch data { + case .set: + break + case let .notSet(pendingEmail): + if pendingEmail == nil { + var phoneNumber: String? + if case let .user(user) = peer { + phoneNumber = user.phone + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = TwoFactorAuthSplashScreen(sharedContext: context.sharedContext, engine: .authorized(context.engine), mode: .intro(.init( + title: presentationData.strings.TwoFactorSetup_Intro_Title, + text: presentationData.strings.TwoFactorSetup_Intro_Text, + actionText: presentationData.strings.TwoFactorSetup_Intro_Action, + doneText: presentationData.strings.TwoFactorSetup_Done_Action, + phoneNumber: phoneNumber + ))) - pushControllerImpl?(controller, true) - return + pushControllerImpl?(controller, true) + return + } } } - } - let controller = twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: false, data: data.flatMap({ Signal.single(.access(configuration: $0)) }))) - pushControllerImpl?(controller, true) + let controller = twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: false, data: data.flatMap({ Signal.single(.access(configuration: $0)) }))) + pushControllerImpl?(controller, true) + }) }, openActiveSessions: { pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: true), true) }, toggleArchiveAndMuteNonContacts: { archiveValue in diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index dfc7d821a3..46e9f09be4 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -21,7 +21,8 @@ public func makeSetupTwoFactorAuthController(context: AccountContext) -> ViewCon title: presentationData.strings.TwoFactorSetup_Intro_Title, text: presentationData.strings.TwoFactorSetup_Intro_Text, actionText: presentationData.strings.TwoFactorSetup_Intro_Action, - doneText: presentationData.strings.TwoFactorSetup_Done_Action + doneText: presentationData.strings.TwoFactorSetup_Done_Action, + phoneNumber: nil ))) return controller } diff --git a/submodules/TelegramCore/Sources/State/SynchronizeRecentlyUsedMediaOperations.swift b/submodules/TelegramCore/Sources/State/SynchronizeRecentlyUsedMediaOperations.swift index 714ebe6242..fd0ef9b6aa 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizeRecentlyUsedMediaOperations.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizeRecentlyUsedMediaOperations.swift @@ -15,15 +15,21 @@ func addSynchronizeRecentlyUsedMediaOperation(transaction: Transaction, category } let peerId = PeerId(0) - var topOperation: (SynchronizeRecentlyUsedMediaOperation, Int32)? + var removeOperations: [(SynchronizeRecentlyUsedMediaOperation, Int32)] = [] transaction.operationLogEnumerateEntries(peerId: peerId, tag: tag, { entry in if let operation = entry.contents as? SynchronizeRecentlyUsedMediaOperation { - topOperation = (operation, entry.tagLocalIndex) + if case .sync = operation.content { + removeOperations.append((operation, entry.tagLocalIndex)) + return true + } else { + return false + } + } else { + return false } - return false }) - if let (topOperation, topLocalIndex) = topOperation, case .sync = topOperation.content { + for (_, topLocalIndex) in removeOperations { let _ = transaction.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: topLocalIndex) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 9f0013fb96..621cc090f1 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -1342,7 +1342,7 @@ private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, Pager let itemFrame = itemLayout.frame(at: index) itemLayer.frame = itemFrame - itemLayer.isVisibleForAnimations = true + itemLayer.isVisibleForAnimations = context.sharedContext.energyUsageSettings.loopEmoji } } @@ -2160,7 +2160,7 @@ private final class EmptySearchResultsView: UIView { animationCache: context.animationCache, animationRenderer: context.animationRenderer, content: .animation(content: .file(file: file), size: CGSize(width: 32.0, height: 32.0), placeholderColor: titleColor, themeColor: nil, loopMode: .forever), - isVisibleForAnimations: true, + isVisibleForAnimations: context.sharedContext.energyUsageSettings.loopEmoji, action: nil )), environment: {}, @@ -5795,7 +5795,7 @@ public final class EmojiPagerContentComponent: Component { placeholderView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) } - itemLayer.isVisibleForAnimations = keyboardChildEnvironment.isContentInFocus + itemLayer.isVisibleForAnimations = keyboardChildEnvironment.isContentInFocus && component.context.sharedContext.energyUsageSettings.loopEmoji } } if itemGroup.fillWithLoadingPlaceholders { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift index c87fe101c6..34303c44e6 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift @@ -167,7 +167,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { itemLayer.layerTintColor = component.theme.list.itemAccentColor.cgColor } - itemLayer.isVisibleForAnimations = itemEnvironment.isContentInFocus + itemLayer.isVisibleForAnimations = itemEnvironment.isContentInFocus && component.context.sharedContext.energyUsageSettings.loopEmoji } if itemEnvironment.isExpanded { diff --git a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift index e7140a7d5f..0f14ae00ab 100644 --- a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift +++ b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift @@ -93,6 +93,8 @@ public final class TextNodeWithEntities { public let textNode: TextNode private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:] + private var enableLooping: Bool = true + public var visibilityRect: CGRect? { didSet { if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibilityRect { @@ -107,7 +109,7 @@ public final class TextNodeWithEntities { } else { isItemVisible = false } - itemLayer.isVisibleForAnimations = isItemVisible + itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible } } } @@ -218,6 +220,8 @@ public final class TextNodeWithEntities { } private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, attemptSynchronousLoad: Bool) { + self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji + var nextIndexById: [Int64: Int] = [:] var validIds: [InlineStickerItemLayer.Key] = [] @@ -250,7 +254,7 @@ public final class TextNodeWithEntities { self.inlineStickerItemLayers[id] = itemLayer self.textNode.layer.addSublayer(itemLayer) - itemLayer.isVisibleForAnimations = self.isItemVisible(itemRect: itemFrame) + itemLayer.isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame) } itemLayer.frame = itemFrame @@ -284,6 +288,8 @@ public class ImmediateTextNodeWithEntities: TextNode { public var cutout: TextNodeCutout? public var displaySpoilers = false + private var enableLooping: Bool = true + public var arguments: TextNodeWithEntities.Arguments? private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:] @@ -293,7 +299,7 @@ public class ImmediateTextNodeWithEntities: TextNode { if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibility { for (_, itemLayer) in self.inlineStickerItemLayers { let isItemVisible: Bool = self.visibility - itemLayer.isVisibleForAnimations = isItemVisible + itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible } } } @@ -385,6 +391,8 @@ public class ImmediateTextNodeWithEntities: TextNode { } private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor) { + self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji + var nextIndexById: [Int64: Int] = [:] var validIds: [InlineStickerItemLayer.Key] = [] @@ -415,7 +423,7 @@ public class ImmediateTextNodeWithEntities: TextNode { self.inlineStickerItemLayers[id] = itemLayer self.layer.addSublayer(itemLayer) - itemLayer.isVisibleForAnimations = self.visibility + itemLayer.isVisibleForAnimations = self.enableLooping && self.visibility } itemLayer.frame = itemFrame diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 7f80ca3dcb..97aa66f2ed 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1646,6 +1646,9 @@ private func extractAccountManagerState(records: AccountRecordsView deliverOnMainQueue).start(next: { [weak self] peerView in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index e51a3e7fe8..bf6ef37eb3 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -152,6 +152,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { return self._automaticMediaDownloadSettings.get() } + public var energyUsageSettings: EnergyUsageSettings { + return self.currentAutomaticMediaDownloadSettings.with({ $0 }).energyUsageSettings + } + public let currentAutodownloadSettings: Atomic private let _autodownloadSettings = Promise() private var currentAutodownloadSettingsDisposable = MetaDisposable() diff --git a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift index d8bb198bb3..7e65ec0a63 100644 --- a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift @@ -258,6 +258,73 @@ public struct MediaAutoSaveSettings: Codable, Equatable { } } +public struct EnergyUsageSettings: Codable, Equatable { + private enum CodingKeys: CodingKey { + case loopEmoji + case playVideoAvatars + case fullTranslucency + case extendBackgroundWork + case synchronizeInBackground + case autodownloadInBackground + } + + public static var `default`: EnergyUsageSettings { + return EnergyUsageSettings( + loopEmoji: true, + playVideoAvatars: true, + fullTranslucency: true, + extendBackgroundWork: true, + synchronizeInBackground: true, + autodownloadInBackground: true + ) + } + + public var loopEmoji: Bool + public var playVideoAvatars: Bool + public var fullTranslucency: Bool + public var extendBackgroundWork: Bool + public var synchronizeInBackground: Bool + public var autodownloadInBackground: Bool + + public init( + loopEmoji: Bool, + playVideoAvatars: Bool, + fullTranslucency: Bool, + extendBackgroundWork: Bool, + synchronizeInBackground: Bool, + autodownloadInBackground: Bool + ) { + self.loopEmoji = loopEmoji + self.playVideoAvatars = playVideoAvatars + self.fullTranslucency = fullTranslucency + self.extendBackgroundWork = extendBackgroundWork + self.synchronizeInBackground = synchronizeInBackground + self.autodownloadInBackground = autodownloadInBackground + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.loopEmoji = try container.decodeIfPresent(Bool.self, forKey: .loopEmoji) ?? EnergyUsageSettings.default.loopEmoji + self.playVideoAvatars = try container.decodeIfPresent(Bool.self, forKey: .playVideoAvatars) ?? EnergyUsageSettings.default.playVideoAvatars + self.fullTranslucency = try container.decodeIfPresent(Bool.self, forKey: .fullTranslucency) ?? EnergyUsageSettings.default.fullTranslucency + self.extendBackgroundWork = try container.decodeIfPresent(Bool.self, forKey: .extendBackgroundWork) ?? EnergyUsageSettings.default.extendBackgroundWork + self.synchronizeInBackground = try container.decodeIfPresent(Bool.self, forKey: .synchronizeInBackground) ?? EnergyUsageSettings.default.synchronizeInBackground + self.autodownloadInBackground = try container.decodeIfPresent(Bool.self, forKey: .autodownloadInBackground) ?? EnergyUsageSettings.default.autodownloadInBackground + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.loopEmoji, forKey: .loopEmoji) + try container.encode(self.playVideoAvatars, forKey: .playVideoAvatars) + try container.encode(self.fullTranslucency, forKey: .fullTranslucency) + try container.encode(self.extendBackgroundWork, forKey: .extendBackgroundWork) + try container.encode(self.synchronizeInBackground, forKey: .synchronizeInBackground) + try container.encode(self.autodownloadInBackground, forKey: .autodownloadInBackground) + } +} + public struct MediaAutoDownloadSettings: Codable, Equatable { public var presets: MediaAutoDownloadPresets public var cellular: MediaAutoDownloadConnection @@ -267,27 +334,30 @@ public struct MediaAutoDownloadSettings: Codable, Equatable { public var autoplayVideos: Bool public var downloadInBackground: Bool + public var energyUsageSettings: EnergyUsageSettings + public static var defaultSettings: MediaAutoDownloadSettings { let mb: Int64 = 1024 * 1024 let presets = MediaAutoDownloadPresets(low: MediaAutoDownloadCategories(basePreset: .low, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false), - video: MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 1 * mb, predownload: false), - file: MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 1 * mb, predownload: false)), - medium: MediaAutoDownloadCategories(basePreset: .medium, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false), - video: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: Int64(2.5 * CGFloat(mb)), predownload: false), - file: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false)), - high: MediaAutoDownloadCategories(basePreset: .high, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false), - video: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 10 * mb, predownload: true), - file: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 3 * mb, predownload: false))) - return MediaAutoDownloadSettings(presets: presets, cellular: MediaAutoDownloadConnection(enabled: true, preset: .medium, custom: nil), wifi: MediaAutoDownloadConnection(enabled: true, preset: .high, custom: nil), autoplayGifs: true, autoplayVideos: true, downloadInBackground: true) + video: MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 1 * mb, predownload: false), + file: MediaAutoDownloadCategory(contacts: false, otherPrivate: false, groups: false, channels: false, sizeLimit: 1 * mb, predownload: false)), + medium: MediaAutoDownloadCategories(basePreset: .medium, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false), + video: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: Int64(2.5 * CGFloat(mb)), predownload: false), + file: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false)), + high: MediaAutoDownloadCategories(basePreset: .high, photo: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 1 * mb, predownload: false), + video: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 10 * mb, predownload: true), + file: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 3 * mb, predownload: false))) + return MediaAutoDownloadSettings(presets: presets, cellular: MediaAutoDownloadConnection(enabled: true, preset: .medium, custom: nil), wifi: MediaAutoDownloadConnection(enabled: true, preset: .high, custom: nil), autoplayGifs: true, autoplayVideos: true, downloadInBackground: true, energyUsageSettings: EnergyUsageSettings.default) } - public init(presets: MediaAutoDownloadPresets, cellular: MediaAutoDownloadConnection, wifi: MediaAutoDownloadConnection, autoplayGifs: Bool, autoplayVideos: Bool, downloadInBackground: Bool) { + public init(presets: MediaAutoDownloadPresets, cellular: MediaAutoDownloadConnection, wifi: MediaAutoDownloadConnection, autoplayGifs: Bool, autoplayVideos: Bool, downloadInBackground: Bool, energyUsageSettings: EnergyUsageSettings) { self.presets = presets self.cellular = cellular self.wifi = wifi self.autoplayGifs = autoplayGifs self.autoplayVideos = autoplayGifs self.downloadInBackground = downloadInBackground + self.energyUsageSettings = energyUsageSettings } public init(from decoder: Decoder) throws { @@ -303,6 +373,8 @@ public struct MediaAutoDownloadSettings: Codable, Equatable { self.autoplayGifs = try container.decode(Int32.self, forKey: "autoplayGifs") != 0 self.autoplayVideos = try container.decode(Int32.self, forKey: "autoplayVideos") != 0 self.downloadInBackground = try container.decode(Int32.self, forKey: "downloadInBackground") != 0 + + self.energyUsageSettings = (try container.decodeIfPresent(EnergyUsageSettings.self, forKey: "energyUsageSettings")) ?? EnergyUsageSettings.default } public func encode(to encoder: Encoder) throws { @@ -313,6 +385,7 @@ public struct MediaAutoDownloadSettings: Codable, Equatable { try container.encode((self.autoplayGifs ? 1 : 0) as Int32, forKey: "autoplayGifs") try container.encode((self.autoplayVideos ? 1 : 0) as Int32, forKey: "autoplayVideos") try container.encode((self.downloadInBackground ? 1 : 0) as Int32, forKey: "downloadInBackground") + try container.encode(self.energyUsageSettings, forKey: "energyUsageSettings") } public func connectionSettings(for networkType: MediaAutoDownloadNetworkType) -> MediaAutoDownloadConnection {