diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 39f72e18ca..317dd4615d 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -872,6 +872,7 @@ public protocol SharedAccountContext: AnyObject { func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController + func makeArchiveSettingsController(context: AccountContext) -> ViewController func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 43a8681831..a8c8cf1028 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -98,6 +98,7 @@ swift_library( "//submodules/TelegramUI/Components/FullScreenEffectView", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen", + "//submodules/TelegramUI/Components/Settings/ArchiveInfoScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 4725988abe..086beed7d0 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -48,6 +48,7 @@ import ChatFolderLinkPreviewScreen import StoryContainerScreen import FullScreenEffectView import PeerInfoStoryGridScreen +import ArchiveInfoScreen private final class ContextControllerContentSourceImpl: ContextControllerContentSource { let controller: ViewController @@ -2666,9 +2667,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let _ = (self.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.id), - TelegramEngine.EngineData.Item.NotificationSettings.Global() + TelegramEngine.EngineData.Item.NotificationSettings.Global(), + TelegramEngine.EngineData.Item.Contacts.Top() ) - |> deliverOnMainQueue).start(next: { [weak self] notificationSettings, globalSettings in + |> deliverOnMainQueue).start(next: { [weak self] notificationSettings, globalSettings, topSearchPeers in guard let self else { return } @@ -2748,7 +2750,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) }))) - let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings()) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers) items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in @@ -3260,6 +3262,65 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) } + func openArchiveMoreMenu(sourceView: UIView, gesture: ContextGesture?) { + let _ = ( + self.context.engine.messages.chatList(group: .archive, count: 10) |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] archiveChatList in + guard let self else { + return + } + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + + var items: [ContextMenuItem] = [] + + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "Archive Settings", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.push(self.context.sharedContext.makeArchiveSettingsController(context: self.context)) + }))) + + if !archiveChatList.items.isEmpty { + items.append(.action(ContextMenuActionItem(text: "How Does It Work?", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/Question"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy() + ) + |> deliverOnMainQueue).start(next: { [weak self] settings in + guard let self else { + return + } + self.push(ArchiveInfoScreen(context: self.context, settings: settings)) + }) + }))) + items.append(.action(ContextMenuActionItem(text: "Select Chats", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.editPressed() + }))) + } + + let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + self.presentInGlobalOverlay(contextController) + }) + } + private var initializedFilters = false private func reloadFilters(firstUpdate: (() -> Void)? = nil) { let filterItems = chatListFilterItems(context: self.context) @@ -5953,10 +6014,19 @@ private final class ChatListLocationContext { self.storyButton = nil } } else { - self.rightButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( - content: .text(title: presentationData.strings.Common_Edit, isBold: false), - pressed: { [weak self] _ in - self?.parentController?.editPressed() + let parentController = self.parentController + self.rightButton = AnyComponentWithIdentity(id: "more", component: AnyComponent(NavigationButtonComponent( + content: .more, + pressed: { [weak parentController] sourceView in + if let primaryContext = parentController?.primaryContext { + primaryContext.performMoreAction(sourceView: sourceView) + } + }, + contextAction: { [weak self] sourceView, gesture in + guard let self, let parentController = self.parentController else { + return + } + parentController.openArchiveMoreMenu(sourceView: sourceView, gesture: gesture) } ))) } @@ -6147,8 +6217,10 @@ private final class ChatListLocationContext { return } switch self.location { - case .chatList: - break + case let .chatList(mode): + if case .archive = mode { + parentController.openArchiveMoreMenu(sourceView: sourceView, gesture: nil) + } case let .forum(peerId): ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: parentController, isViewingAsTopics: true, sourceView: sourceView, gesture: nil) } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 4f4cb6ec88..bf054fccd2 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -341,6 +341,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { private let becameEmpty: (ChatListFilter?) -> Void private let emptyAction: (ChatListFilter?) -> Void private let secondaryEmptyAction: () -> Void + private let openArchiveSettings: () -> Void private let isInlineMode: Bool private var floatingHeaderOffset: CGFloat? @@ -362,7 +363,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { private(set) var validLayout: (size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat, storiesInset: CGFloat)? - init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, filter: ChatListFilter?, chatListMode: ChatListNodeMode, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void, autoSetReady: Bool) { + init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, filter: ChatListFilter?, chatListMode: ChatListNodeMode, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void, openArchiveSettings: @escaping () -> Void, autoSetReady: Bool) { self.context = context self.controller = controller self.location = location @@ -372,6 +373,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { self.becameEmpty = becameEmpty self.emptyAction = emptyAction self.secondaryEmptyAction = secondaryEmptyAction + self.openArchiveSettings = openArchiveSettings self.isInlineMode = isInlineMode self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: chatListMode, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true, isInlineMode: isInlineMode, autoSetReady: autoSetReady) @@ -446,9 +448,11 @@ private final class ChatListContainerItemNode: ASDisplayNode { self?.emptyAction(filter) }, secondaryAction: { self?.secondaryEmptyAction() + }, openArchiveSettings: { + self?.openArchiveSettings() }) strongSelf.emptyNode = emptyNode - strongSelf.addSubnode(emptyNode) + strongSelf.listNode.addSubnode(emptyNode) if let (size, insets, _, _, _, _, _) = strongSelf.validLayout { let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) emptyNode.frame = emptyNodeFrame @@ -752,6 +756,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele private let filterBecameEmpty: (ChatListFilter?) -> Void private let filterEmptyAction: (ChatListFilter?) -> Void private let secondaryEmptyAction: () -> Void + private let openArchiveSettings: () -> Void fileprivate var onStoriesLockedUpdated: ((Bool) -> Void)? @@ -1086,7 +1091,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele var canExpandHiddenItems: (() -> Bool)? public var displayFilterLimit: (() -> Void)? - public init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, chatListMode: ChatListNodeMode = .chatList(appendContacts: true), previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { + public init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, chatListMode: ChatListNodeMode = .chatList(appendContacts: true), previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void, openArchiveSettings: @escaping () -> Void) { self.context = context self.controller = controller self.location = location @@ -1096,6 +1101,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele self.filterBecameEmpty = filterBecameEmpty self.filterEmptyAction = filterEmptyAction self.secondaryEmptyAction = secondaryEmptyAction + self.openArchiveSettings = openArchiveSettings self.controlsHistoryPreload = controlsHistoryPreload self.presentationData = presentationData @@ -1118,6 +1124,8 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele self?.filterEmptyAction(filter) }, secondaryEmptyAction: { [weak self] in self?.secondaryEmptyAction() + }, openArchiveSettings: { [weak self] in + self?.openArchiveSettings() }, autoSetReady: true) self.itemNodes[.all] = itemNode self.addSubnode(itemNode) @@ -1460,6 +1468,8 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele self?.filterEmptyAction(filter) }, secondaryEmptyAction: { [weak self] in self?.secondaryEmptyAction() + }, openArchiveSettings: { [weak self] in + self?.openArchiveSettings() }, autoSetReady: !animated) let disposable = MetaDisposable() self.pendingItemNode = (id, itemNode, disposable) @@ -1590,6 +1600,8 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele self?.filterEmptyAction(filter) }, secondaryEmptyAction: { [weak self] in self?.secondaryEmptyAction() + }, openArchiveSettings: { [weak self] in + self?.openArchiveSettings() }, autoSetReady: false) itemNode.listNode.tempTopInset = self.tempTopInset self.itemNodes[id] = itemNode @@ -1724,12 +1736,15 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { var filterBecameEmpty: ((ChatListFilter?) -> Void)? var filterEmptyAction: ((ChatListFilter?) -> Void)? var secondaryEmptyAction: (() -> Void)? + var openArchiveSettings: (() -> Void)? self.mainContainerNode = ChatListContainerNode(context: context, controller: controller, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, isInlineMode: false, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in filterBecameEmpty?(filter) }, filterEmptyAction: { filter in filterEmptyAction?(filter) }, secondaryEmptyAction: { secondaryEmptyAction?() + }, openArchiveSettings: { + openArchiveSettings?() }) self.controller = controller @@ -1783,6 +1798,13 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { (controller.navigationController as? NavigationController)?.replaceController(controller, with: chatController, animated: false) } + openArchiveSettings = { [weak self] in + guard let self, let controller = self.controller else { + return + } + controller.push(self.context.sharedContext.makeArchiveSettingsController(context: self.context)) + } + self.mainContainerNode.onFilterSwitch = { [weak self] in if let strongSelf = self { strongSelf.controller?.dismissAllUndoControllers() @@ -2433,7 +2455,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { forumPeerId = peerId } - let inlineStackContainerNode = ChatListContainerNode(context: self.context, controller: self.controller, location: location, previewing: false, controlsHistoryPreload: false, isInlineMode: true, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { [weak self] _ in self?.emptyListAction?(forumPeerId) }, secondaryEmptyAction: {}) + let inlineStackContainerNode = ChatListContainerNode(context: self.context, controller: self.controller, location: location, previewing: false, controlsHistoryPreload: false, isInlineMode: true, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { [weak self] _ in self?.emptyListAction?(forumPeerId) }, secondaryEmptyAction: {}, openArchiveSettings: {}) return inlineStackContainerNode } diff --git a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift index 200b8d985d..46e1a8bd29 100644 --- a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift +++ b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift @@ -9,6 +9,11 @@ import AppBundle import SolidRoundedButtonNode import ActivityIndicator import AccountContext +import TelegramCore +import ComponentFlow +import ArchiveInfoScreen +import ComponentDisplayAdapters +import SwiftSignalKit final class ChatListEmptyNode: ASDisplayNode { enum Subject { @@ -19,6 +24,11 @@ final class ChatListEmptyNode: ASDisplayNode { } private let action: () -> Void private let secondaryAction: () -> Void + private let openArchiveSettings: () -> Void + + private let context: AccountContext + private var theme: PresentationTheme + private var strings: PresentationStrings let subject: Subject private(set) var isLoading: Bool @@ -29,14 +39,24 @@ final class ChatListEmptyNode: ASDisplayNode { private let secondaryButtonNode: HighlightableButtonNode private let activityIndicator: ActivityIndicator + private var emptyArchive: ComponentView? + private var animationSize: CGSize = CGSize() private var buttonIsHidden: Bool private var validLayout: CGSize? - init(context: AccountContext, subject: Subject, isLoading: Bool, theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void, secondaryAction: @escaping () -> Void) { + private var globalPrivacySettings: GlobalPrivacySettings = .default + private var archiveSettingsDisposable: Disposable? + + init(context: AccountContext, subject: Subject, isLoading: Bool, theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void, secondaryAction: @escaping () -> Void, openArchiveSettings: @escaping () -> Void) { + self.context = context + self.theme = theme + self.strings = strings + self.action = action self.secondaryAction = secondaryAction + self.openArchiveSettings = openArchiveSettings self.subject = subject self.isLoading = isLoading @@ -81,16 +101,20 @@ final class ChatListEmptyNode: ASDisplayNode { super.init() - self.addSubnode(self.animationNode) - self.addSubnode(self.textNode) - self.addSubnode(self.descriptionNode) - self.addSubnode(self.buttonNode) - self.addSubnode(self.secondaryButtonNode) - self.addSubnode(self.activityIndicator) - - self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 248, height: 248, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) self.animationSize = CGSize(width: 124.0, height: 124.0) - self.animationNode.visibility = true + + if case .archive = subject { + } else { + self.addSubnode(self.animationNode) + self.addSubnode(self.textNode) + self.addSubnode(self.descriptionNode) + self.addSubnode(self.buttonNode) + self.addSubnode(self.secondaryButtonNode) + self.addSubnode(self.activityIndicator) + + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 248, height: 248, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + self.animationNode.visibility = true + } self.animationNode.isHidden = self.isLoading self.textNode.isHidden = self.isLoading @@ -108,6 +132,25 @@ final class ChatListEmptyNode: ASDisplayNode { self.updateThemeAndStrings(theme: theme, strings: strings) self.animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.animationTapGesture(_:)))) + + if case .archive = subject { + self.archiveSettingsDisposable = (context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy() + ) + |> deliverOnMainQueue).start(next: { [weak self] settings in + guard let self else { + return + } + self.globalPrivacySettings = settings + if let size = self.validLayout { + self.updateLayout(size: size, transition: .immediate) + } + }) + } + } + + deinit { + self.archiveSettingsDisposable?.dispose() } @objc private func buttonPressed() { @@ -131,6 +174,9 @@ final class ChatListEmptyNode: ASDisplayNode { } func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.theme = theme + self.strings = strings + let text: String var descriptionText = "" let buttonText: String? @@ -185,6 +231,42 @@ final class ChatListEmptyNode: ASDisplayNode { func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { self.validLayout = size + if case .archive = self.subject { + let emptyArchive: ComponentView + if let current = self.emptyArchive { + emptyArchive = current + } else { + emptyArchive = ComponentView() + self.emptyArchive = emptyArchive + } + let emptyArchiveSize = emptyArchive.update( + transition: Transition(transition), + component: AnyComponent(ArchiveInfoContentComponent( + theme: self.theme, + strings: self.strings, + settings: self.globalPrivacySettings, + openSettings: { [weak self] in + guard let self else { + return + } + self.openArchiveSettings() + } + )), + environment: { + }, + containerSize: CGSize(width: size.width, height: 1000.0) + ) + if let emptyArchiveView = emptyArchive.view { + if emptyArchiveView.superview == nil { + self.view.addSubview(emptyArchiveView) + } + transition.updateFrame(view: emptyArchiveView, frame: CGRect(origin: CGPoint(x: floor((size.width - emptyArchiveSize.width) * 0.5), y: 41.0), size: emptyArchiveSize)) + } + } else if let emptyArchive = self.emptyArchive { + self.emptyArchive = nil + emptyArchive.view?.removeFromSuperview() + } + let indicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0)) transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: floor((size.height - indicatorSize.height - 50.0) / 2.0)), size: indicatorSize)) @@ -253,6 +335,11 @@ final class ChatListEmptyNode: ASDisplayNode { if self.secondaryButtonNode.frame.contains(point), !self.secondaryButtonNode.isHidden { return self.secondaryButtonNode.view.hitTest(self.view.convert(point, to: self.secondaryButtonNode.view), with: event) } + if let emptyArchiveView = self.emptyArchive?.view { + if let result = emptyArchiveView.hitTest(self.view.convert(point, to: emptyArchiveView), with: event) { + return result + } + } return nil } } diff --git a/submodules/ContactListUI/Sources/ContactContextMenus.swift b/submodules/ContactListUI/Sources/ContactContextMenus.swift index 62f81c6b84..40cd1720f0 100644 --- a/submodules/ContactListUI/Sources/ContactContextMenus.swift +++ b/submodules/ContactListUI/Sources/ContactContextMenus.swift @@ -20,9 +20,10 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con TelegramEngine.EngineData.Item.Peer.AreVoiceCallsAvailable(id: peerId), TelegramEngine.EngineData.Item.Peer.AreVideoCallsAvailable(id: peerId), TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId), - TelegramEngine.EngineData.Item.NotificationSettings.Global() + TelegramEngine.EngineData.Item.NotificationSettings.Global(), + TelegramEngine.EngineData.Item.Contacts.Top() ) - |> map { [weak contactsController] peer, areVoiceCallsAvailable, areVideoCallsAvailable, notificationSettings, globalSettings -> [ContextMenuItem] in + |> map { [weak contactsController] peer, areVoiceCallsAvailable, areVideoCallsAvailable, notificationSettings, globalSettings, topSearchPeers -> [ContextMenuItem] in guard let peer else { return [] } @@ -47,7 +48,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con }) }))) - let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings()) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers) items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) diff --git a/submodules/SettingsUI/Sources/ArchiveSettingsController.swift b/submodules/SettingsUI/Sources/ArchiveSettingsController.swift new file mode 100644 index 0000000000..b55a39fa17 --- /dev/null +++ b/submodules/SettingsUI/Sources/ArchiveSettingsController.swift @@ -0,0 +1,216 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import ItemListUI +import PresentationDataUtils +import AccountContext +import UndoUI + +private final class ArchiveSettingsControllerArguments { + let updateUnmuted: (Bool) -> Void + let updateFolders: (Bool) -> Void + let updateUnknown: (Bool?) -> Void + + init( + updateUnmuted: @escaping (Bool) -> Void, + updateFolders: @escaping (Bool) -> Void, + updateUnknown: @escaping (Bool?) -> Void + ) { + self.updateUnmuted = updateUnmuted + self.updateFolders = updateFolders + self.updateUnknown = updateUnknown + } +} + +private enum ArchiveSettingsSection: Int32 { + case unmuted + case folders + case unknown +} + +private enum ArchiveSettingsControllerEntry: ItemListNodeEntry { + case unmutedHeader + case unmutedValue(Bool) + case unmutedFooter + + case foldersHeader + case foldersValue(Bool) + case foldersFooter + + case unknownHeader + case unknownValue(isOn: Bool, isLocked: Bool) + case unknownFooter + + var section: ItemListSectionId { + switch self { + case .unmutedHeader, .unmutedValue, .unmutedFooter: + return ArchiveSettingsSection.unmuted.rawValue + case .foldersHeader, .foldersValue, .foldersFooter: + return ArchiveSettingsSection.folders.rawValue + case .unknownHeader, .unknownValue, .unknownFooter: + return ArchiveSettingsSection.unknown.rawValue + } + } + + var stableId: Int32 { + switch self { + case .unmutedHeader: + return 0 + case .unmutedValue: + return 1 + case .unmutedFooter: + return 2 + case .foldersHeader: + return 3 + case .foldersValue: + return 4 + case .foldersFooter: + return 5 + case .unknownHeader: + return 6 + case .unknownValue: + return 7 + case .unknownFooter: + return 8 + } + } + + static func <(lhs: ArchiveSettingsControllerEntry, rhs: ArchiveSettingsControllerEntry) -> Bool { + return lhs.stableId < rhs.stableId + } + + func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { + let arguments = arguments as! ArchiveSettingsControllerArguments + //TODO:localize + switch self { + case .unmutedHeader: + return ItemListSectionHeaderItem(presentationData: presentationData, text: "UNMUTED CHATS", sectionId: self.section) + case let .unmutedValue(value): + return ItemListSwitchItem(presentationData: presentationData, title: "Always Keep Archived", value: value, sectionId: self.section, style: .blocks, updated: { value in + arguments.updateUnmuted(value) + }) + case .unmutedFooter: + return ItemListTextItem(presentationData: presentationData, text: .markdown("Keep archived chats in the Archive even if they are unmuted and get a new message."), sectionId: self.section) + case .foldersHeader: + return ItemListSectionHeaderItem(presentationData: presentationData, text: "CHATS FROM FOLDERS", sectionId: self.section) + case let .foldersValue(value): + return ItemListSwitchItem(presentationData: presentationData, title: "Always Keep Archived", value: value, sectionId: self.section, style: .blocks, updated: { value in + arguments.updateFolders(value) + }) + case .foldersFooter: + return ItemListTextItem(presentationData: presentationData, text: .markdown("Keep archived chats from folders in the Archive even if they are unmuted and get a new message."), sectionId: self.section) + case .unknownHeader: + return ItemListSectionHeaderItem(presentationData: presentationData, text: "NEW CHATS FROM UNKNOWN USERS", sectionId: self.section) + case let .unknownValue(isOn, isLocked): + return ItemListSwitchItem(presentationData: presentationData, title: "Always Keep Archived", value: isOn, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { value in + arguments.updateUnknown(value) + }, activatedWhileDisabled: { + arguments.updateUnknown(nil) + }) + case .unknownFooter: + return ItemListTextItem(presentationData: presentationData, text: .markdown("Automatically archive and mute new private chats, groups and channels from non-contacts."), sectionId: self.section) + } + } +} + +private func archiveSettingsControllerEntries( + presentationData: PresentationData, + settings: GlobalPrivacySettings, + isPremium: Bool, + isPremiumEnabled: Bool +) -> [ArchiveSettingsControllerEntry] { + var entries: [ArchiveSettingsControllerEntry] = [] + + entries.append(.unmutedHeader) + entries.append(.unmutedValue(settings.keepArchivedUnmuted)) + entries.append(.unmutedFooter) + + if !settings.keepArchivedUnmuted { + entries.append(.foldersHeader) + entries.append(.foldersValue(settings.keepArchivedFolders)) + entries.append(.foldersFooter) + } + + if isPremium || isPremiumEnabled { + entries.append(.unknownHeader) + entries.append(.unknownValue(isOn: isPremium && settings.automaticallyArchiveAndMuteNonContacts, isLocked: !isPremium)) + entries.append(.unknownFooter) + } + + return entries +} + +public func archiveSettingsController(context: AccountContext) -> ViewController { + let updateDisposable = MetaDisposable() + + updateDisposable.set(context.engine.privacy.requestAccountPrivacySettings().start()) + + var presentUndoImpl: ((UndoOverlayContent) -> Void)? + var presentPremiumImpl: (() -> Void)? + + let arguments = ArchiveSettingsControllerArguments( + updateUnmuted: { value in + let _ = context.engine.privacy.updateAccountKeepArchivedUnmuted(value: value).start() + }, + updateFolders: { value in + let _ = context.engine.privacy.updateAccountKeepArchivedFolders(value: value).start() + }, + updateUnknown: { value in + if let value { + let _ = context.engine.privacy.updateAccountAutoArchiveChats(value: value).start() + } else { + presentUndoImpl?(.premiumPaywall(title: nil, text: "This setting is available only to the subscribers of [Telegram Premium]().", customUndoText: nil, timeout: nil, linkAction: { _ in + presentPremiumImpl?() + })) + } + } + ) + + let signal = combineLatest(queue: .mainQueue(), + context.sharedContext.presentationData, + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy()), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + ) + |> deliverOnMainQueue + |> map { presentationData, settings, appConfiguration, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in + let isPremium = accountPeer?.isPremium ?? false + let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled + + //TODO:localize + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Archive Settings"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archiveSettingsControllerEntries( + presentationData: presentationData, + settings: settings, + isPremium: isPremium, + isPremiumEnabled: !isPremiumDisabled + ), style: .blocks, animateChanges: true) + + return (controllerState, (listState, arguments)) + } + + let controller = ItemListController(context: context, state: signal) + + presentUndoImpl = { [weak controller] content in + guard let controller else { + return + } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, action: { _ in + return false + }), in: .current) + } + presentPremiumImpl = { [weak controller] in + guard let controller else { + return + } + let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .settings) + controller.push(premiumController) + } + + return controller +} diff --git a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift index 4c44a9cbae..01cc16da5d 100644 --- a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift +++ b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift @@ -444,10 +444,10 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor title += ", " } //TODO:localize - if case .show = value.settings.displayPreviews { - title += "Show Names" + if case .show = value.settings.storySettings.hideSender { + title += "Show Name" } else { - title += "Hide Names" + title += "Hide Name" } } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index bec2dda328..d3de884eaa 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -599,7 +599,7 @@ private func privacyAndSecurityControllerEntries( if let automaticallyArchiveAndMuteNonContacts = state.updatingAutomaticallyArchiveAndMuteNonContacts { automaticallyArchiveAndMuteNonContactsValue = automaticallyArchiveAndMuteNonContacts } else { - automaticallyArchiveAndMuteNonContactsValue = privacySettings.automaticallyArchiveAndMuteNonContacts + automaticallyArchiveAndMuteNonContactsValue = privacySettings.globalSettings.automaticallyArchiveAndMuteNonContacts } entries.append(.autoArchive(presentationData.strings.PrivacySettings_AutoArchive, automaticallyArchiveAndMuteNonContactsValue)) @@ -773,7 +773,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -796,7 +796,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -833,7 +833,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, voiceCallsP2P: updatedCallsPrivacy, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, voiceCallsP2P: updatedCallsPrivacy, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -860,7 +860,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: updated, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: updated, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -883,7 +883,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: updated, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: updated, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -906,7 +906,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: updated, phoneDiscoveryEnabled: updatedDiscoveryEnabled ?? value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: updated, phoneDiscoveryEnabled: updatedDiscoveryEnabled ?? value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -936,7 +936,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, bio: value.bio, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -976,7 +976,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: updated, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: updated, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -1041,7 +1041,10 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, automaticallyArchiveAndMuteNonContacts: archiveValue, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + var globalSettings = value.globalSettings + globalSettings.automaticallyArchiveAndMuteNonContacts = archiveValue + + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, globalSettings: globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -1080,7 +1083,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: timeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: timeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) } return .complete() } @@ -1141,7 +1144,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue |> mapToSignal { value -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: updatedValue == 0 ? nil : updatedValue))) + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: updatedValue == 0 ? nil : updatedValue))) } return .complete() } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index e28d138652..016077496c 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -252,7 +252,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1107729093] = { return Api.Game.parse_game($0) } dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) } dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) } - dict[-1096616924] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) } + dict[1934380235] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) } dict[-711498484] = { return Api.GroupCall.parse_groupCall($0) } dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) } dict[-341428482] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) } @@ -795,7 +795,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[2008112412] = { return Api.StickerSetCovered.parse_stickerSetNoCovered($0) } dict[1445635639] = { return Api.StoryItem.parse_storyItem($0) } dict[1374088783] = { return Api.StoryItem.parse_storyItemDeleted($0) } - dict[1764886178] = { return Api.StoryItem.parse_storyItemSkipped($0) } + dict[-5388013] = { return Api.StoryItem.parse_storyItemSkipped($0) } dict[-1491424062] = { return Api.StoryView.parse_storyView($0) } dict[-748199729] = { return Api.StoryViews.parse_storyViews($0) } dict[1964978502] = { return Api.TextWithEntities.parse_textWithEntities($0) } diff --git a/submodules/TelegramApi/Sources/Api21.swift b/submodules/TelegramApi/Sources/Api21.swift index 624504b10e..14a69394a4 100644 --- a/submodules/TelegramApi/Sources/Api21.swift +++ b/submodules/TelegramApi/Sources/Api21.swift @@ -366,7 +366,7 @@ public extension Api { indirect enum StoryItem: TypeConstructorDescription { case storyItem(flags: Int32, id: Int32, date: Int32, expireDate: Int32, caption: String?, entities: [Api.MessageEntity]?, media: Api.MessageMedia, privacy: [Api.PrivacyRule]?, views: Api.StoryViews?) case storyItemDeleted(id: Int32) - case storyItemSkipped(id: Int32, date: Int32, expireDate: Int32) + case storyItemSkipped(flags: Int32, id: Int32, date: Int32, expireDate: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -398,10 +398,11 @@ public extension Api { } serializeInt32(id, buffer: buffer, boxed: false) break - case .storyItemSkipped(let id, let date, let expireDate): + case .storyItemSkipped(let flags, let id, let date, let expireDate): if boxed { - buffer.appendInt32(1764886178) + buffer.appendInt32(-5388013) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) serializeInt32(expireDate, buffer: buffer, boxed: false) @@ -415,8 +416,8 @@ public extension Api { return ("storyItem", [("flags", flags as Any), ("id", id as Any), ("date", date as Any), ("expireDate", expireDate as Any), ("caption", caption as Any), ("entities", entities as Any), ("media", media as Any), ("privacy", privacy as Any), ("views", views as Any)]) case .storyItemDeleted(let id): return ("storyItemDeleted", [("id", id as Any)]) - case .storyItemSkipped(let id, let date, let expireDate): - return ("storyItemSkipped", [("id", id as Any), ("date", date as Any), ("expireDate", expireDate as Any)]) + case .storyItemSkipped(let flags, let id, let date, let expireDate): + return ("storyItemSkipped", [("flags", flags as Any), ("id", id as Any), ("date", date as Any), ("expireDate", expireDate as Any)]) } } @@ -481,11 +482,14 @@ public extension Api { _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoryItem.storyItemSkipped(id: _1!, date: _2!, expireDate: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StoryItem.storyItemSkipped(flags: _1!, id: _2!, date: _3!, expireDate: _4!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index 4043178c98..6c3196fbe2 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -462,38 +462,32 @@ public extension Api { } public extension Api { enum GlobalPrivacySettings: TypeConstructorDescription { - case globalPrivacySettings(flags: Int32, archiveAndMuteNewNoncontactPeers: Api.Bool?) + case globalPrivacySettings(flags: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .globalPrivacySettings(let flags, let archiveAndMuteNewNoncontactPeers): + case .globalPrivacySettings(let flags): if boxed { - buffer.appendInt32(-1096616924) + buffer.appendInt32(1934380235) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {archiveAndMuteNewNoncontactPeers!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .globalPrivacySettings(let flags, let archiveAndMuteNewNoncontactPeers): - return ("globalPrivacySettings", [("flags", flags as Any), ("archiveAndMuteNewNoncontactPeers", archiveAndMuteNewNoncontactPeers as Any)]) + case .globalPrivacySettings(let flags): + return ("globalPrivacySettings", [("flags", flags as Any)]) } } public static func parse_globalPrivacySettings(_ reader: BufferReader) -> GlobalPrivacySettings? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.GlobalPrivacySettings.globalPrivacySettings(flags: _1!, archiveAndMuteNewNoncontactPeers: _2) + if _c1 { + return Api.GlobalPrivacySettings.globalPrivacySettings(flags: _1!) } else { return nil diff --git a/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift b/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift index d683a76c26..55b02b323f 100644 --- a/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift +++ b/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift @@ -107,11 +107,11 @@ public struct AccountPrivacySettings: Equatable { public let voiceMessages: SelectivePrivacySettings public let bio: SelectivePrivacySettings - public let automaticallyArchiveAndMuteNonContacts: Bool + public let globalSettings: GlobalPrivacySettings public let accountRemovalTimeout: Int32 public let messageAutoremoveTimeout: Int32? - public init(presence: SelectivePrivacySettings, groupInvitations: SelectivePrivacySettings, voiceCalls: SelectivePrivacySettings, voiceCallsP2P: SelectivePrivacySettings, profilePhoto: SelectivePrivacySettings, forwards: SelectivePrivacySettings, phoneNumber: SelectivePrivacySettings, phoneDiscoveryEnabled: Bool, voiceMessages: SelectivePrivacySettings, bio: SelectivePrivacySettings, automaticallyArchiveAndMuteNonContacts: Bool, accountRemovalTimeout: Int32, messageAutoremoveTimeout: Int32?) { + public init(presence: SelectivePrivacySettings, groupInvitations: SelectivePrivacySettings, voiceCalls: SelectivePrivacySettings, voiceCallsP2P: SelectivePrivacySettings, profilePhoto: SelectivePrivacySettings, forwards: SelectivePrivacySettings, phoneNumber: SelectivePrivacySettings, phoneDiscoveryEnabled: Bool, voiceMessages: SelectivePrivacySettings, bio: SelectivePrivacySettings, globalSettings: GlobalPrivacySettings, accountRemovalTimeout: Int32, messageAutoremoveTimeout: Int32?) { self.presence = presence self.groupInvitations = groupInvitations self.voiceCalls = voiceCalls @@ -122,7 +122,7 @@ public struct AccountPrivacySettings: Equatable { self.phoneDiscoveryEnabled = phoneDiscoveryEnabled self.voiceMessages = voiceMessages self.bio = bio - self.automaticallyArchiveAndMuteNonContacts = automaticallyArchiveAndMuteNonContacts + self.globalSettings = globalSettings self.accountRemovalTimeout = accountRemovalTimeout self.messageAutoremoveTimeout = messageAutoremoveTimeout } @@ -158,7 +158,7 @@ public struct AccountPrivacySettings: Equatable { if lhs.bio != rhs.bio { return false } - if lhs.automaticallyArchiveAndMuteNonContacts != rhs.automaticallyArchiveAndMuteNonContacts { + if lhs.globalSettings != rhs.globalSettings { return false } if lhs.accountRemovalTimeout != rhs.accountRemovalTimeout { @@ -246,3 +246,37 @@ func updateGlobalMessageAutoremoveTimeoutSettings(transaction: Transaction, _ f: return PreferencesEntry(updated) }) } + +public struct GlobalPrivacySettings: Equatable, Codable { + public static var `default` = GlobalPrivacySettings( + automaticallyArchiveAndMuteNonContacts: false, + keepArchivedUnmuted: true, + keepArchivedFolders: true + ) + + public var automaticallyArchiveAndMuteNonContacts: Bool + public var keepArchivedUnmuted: Bool + public var keepArchivedFolders: Bool + + public init( + automaticallyArchiveAndMuteNonContacts: Bool, + keepArchivedUnmuted: Bool, + keepArchivedFolders: Bool + ) { + self.automaticallyArchiveAndMuteNonContacts = automaticallyArchiveAndMuteNonContacts + self.keepArchivedUnmuted = keepArchivedUnmuted + self.keepArchivedFolders = keepArchivedFolders + } +} + +func fetchGlobalPrivacySettings(transaction: Transaction) -> GlobalPrivacySettings { + return transaction.getPreferencesEntry(key: PreferencesKeys.globalPrivacySettings)?.get(GlobalPrivacySettings.self) ?? GlobalPrivacySettings.default +} + +func updateGlobalPrivacySettings(transaction: Transaction, _ f: (GlobalPrivacySettings) -> GlobalPrivacySettings) { + transaction.updatePreferencesEntry(key: PreferencesKeys.globalPrivacySettings, { current in + let previous = current?.get(GlobalPrivacySettings.self) ?? GlobalPrivacySettings.default + let updated = f(previous) + return PreferencesEntry(updated) + }) +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index a9f20587cb..353b664c67 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -259,6 +259,7 @@ private enum PreferencesKeyValues: Int32 { case accountSpecificCacheStorageSettings = 28 case linksConfiguration = 29 case chatListFilterUpdates = 30 + case globalPrivacySettings = 31 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -411,6 +412,12 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.chatListFilterUpdates.rawValue) return key }() + + public static let globalPrivacySettings: ValueBoxKey = { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.globalPrivacySettings.rawValue) + return key + }() } private enum SharedDataKeyValues: Int32 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift index 0ab6031eec..a1aefc0da9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift @@ -432,5 +432,26 @@ public extension TelegramEngine.EngineData.Item { return EngineConfiguration.Links(value) } } + + public struct GlobalPrivacy: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = GlobalPrivacySettings + + public init() { + } + + var key: PostboxViewKey { + return .preferences(keys: Set([PreferencesKeys.globalPrivacySettings])) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? PreferencesView else { + preconditionFailure() + } + guard let value = view.values[PreferencesKeys.globalPrivacySettings]?.get(GlobalPrivacySettings.self) else { + return GlobalPrivacySettings.default + } + return value + } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/ContactsData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/ContactsData.swift index 3b786bff35..1062d29651 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/ContactsData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/ContactsData.swift @@ -33,5 +33,28 @@ public extension TelegramEngine.EngineData.Item { return EngineContactList(peers: view.peers.map(EnginePeer.init), presences: view.peerPresences.mapValues(EnginePeer.Presence.init)) } } + + public struct Top: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = Array + + public init() { + } + + var key: PostboxViewKey { + return .cachedItem(cachedRecentPeersEntryId()) + } + + func extract(view: PostboxView) -> [EnginePeer.Id] { + if let value = (view as? CachedItemView)?.value?.get(CachedRecentPeers.self) { + if value.enabled { + return value.ids + } else { + return [] + } + } else { + return [] + } + } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift index 73031b00fe..bd57e5fa11 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift @@ -293,6 +293,50 @@ public extension TelegramEngine { } } + public func subscribe< + T0: TelegramEngineDataItem, + T1: TelegramEngineDataItem, + T2: TelegramEngineDataItem, + T3: TelegramEngineDataItem, + T4: TelegramEngineDataItem, + T5: TelegramEngineDataItem + >( + _ t0: T0, + _ t1: T1, + _ t2: T2, + _ t3: T3, + _ t4: T4, + _ t5: T5 + ) -> Signal< + ( + T0.Result, + T1.Result, + T2.Result, + T3.Result, + T4.Result, + T5.Result + ), + NoError> { + return self._subscribe(items: [ + t0 as! AnyPostboxViewDataItem, + t1 as! AnyPostboxViewDataItem, + t2 as! AnyPostboxViewDataItem, + t3 as! AnyPostboxViewDataItem, + t4 as! AnyPostboxViewDataItem, + t5 as! AnyPostboxViewDataItem + ]) + |> map { results -> (T0.Result, T1.Result, T2.Result, T3.Result, T4.Result, T5.Result) in + return ( + results[0] as! T0.Result, + results[1] as! T1.Result, + results[2] as! T2.Result, + results[3] as! T3.Result, + results[4] as! T4.Result, + results[5] as! T5.Result + ) + } + } + public func get< T0: TelegramEngineDataItem, T1: TelegramEngineDataItem @@ -370,5 +414,32 @@ public extension TelegramEngine { NoError> { return self.subscribe(t0, t1, t2, t3, t4) |> take(1) } + + public func get< + T0: TelegramEngineDataItem, + T1: TelegramEngineDataItem, + T2: TelegramEngineDataItem, + T3: TelegramEngineDataItem, + T4: TelegramEngineDataItem, + T5: TelegramEngineDataItem + >( + _ t0: T0, + _ t1: T1, + _ t2: T2, + _ t3: T3, + _ t4: T4, + _ t5: T5 + ) -> Signal< + ( + T0.Result, + T1.Result, + T2.Result, + T3.Result, + T4.Result, + T5.Result + ), + NoError> { + return self.subscribe(t0, t1, t2, t3, t4, t5) |> take(1) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index d5bfed6d3b..12802cd79e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -295,20 +295,24 @@ public enum Stories { case id case timestamp case expirationTimestamp + case isCloseFriends = "clf" } public let id: Int32 public let timestamp: Int32 public let expirationTimestamp: Int32 + public let isCloseFriends: Bool public init( id: Int32, timestamp: Int32, - expirationTimestamp: Int32 + expirationTimestamp: Int32, + isCloseFriends: Bool ) { self.id = id self.timestamp = timestamp self.expirationTimestamp = expirationTimestamp + self.isCloseFriends = isCloseFriends } public init(from decoder: Decoder) throws { @@ -317,6 +321,7 @@ public enum Stories { self.id = try container.decode(Int32.self, forKey: .id) self.timestamp = try container.decode(Int32.self, forKey: .timestamp) self.expirationTimestamp = try container.decode(Int32.self, forKey: .expirationTimestamp) + self.isCloseFriends = try container.decodeIfPresent(Bool.self, forKey: .isCloseFriends) ?? false } public func encode(to encoder: Encoder) throws { @@ -325,6 +330,7 @@ public enum Stories { try container.encode(self.id, forKey: .id) try container.encode(self.timestamp, forKey: .timestamp) try container.encode(self.expirationTimestamp, forKey: .expirationTimestamp) + try container.encode(self.isCloseFriends, forKey: .isCloseFriends) } public static func ==(lhs: Placeholder, rhs: Placeholder) -> Bool { @@ -337,6 +343,9 @@ public enum Stories { if lhs.expirationTimestamp != rhs.expirationTimestamp { return false } + if lhs.isCloseFriends != rhs.isCloseFriends { + return false + } return true } } @@ -386,8 +395,8 @@ public enum Stories { switch self { case let .item(item): return item.isCloseFriends - case .placeholder: - return false + case let .placeholder(placeholder): + return placeholder.isCloseFriends } } @@ -1252,7 +1261,7 @@ extension Api.StoryItem { return id case let .storyItemDeleted(id): return id - case let .storyItemSkipped(id, _, _): + case let .storyItemSkipped(_, id, _, _): return id } } @@ -1341,8 +1350,9 @@ extension Stories.StoredItem { } else { return nil } - case let .storyItemSkipped(id, date, expireDate): - self = .placeholder(Stories.Placeholder(id: id, timestamp: date, expirationTimestamp: expireDate)) + case let .storyItemSkipped(flags, id, date, expireDate): + let isCloseFriends = (flags & (1 << 8)) != 0 + self = .placeholder(Stories.Placeholder(id: id, timestamp: date, expirationTimestamp: expireDate, isCloseFriends: isCloseFriends)) case .storyItemDeleted: return nil } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift index 6ce2c4976c..384bd0f1b7 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift @@ -62,8 +62,16 @@ func _internal_togglePeerMuted(account: Account, peerId: PeerId, threadId: Int64 } } -public func resolvedAreStoriesMuted(globalSettings: GlobalNotificationSettingsSet, peer: Peer, peerSettings: TelegramPeerNotificationSettings?) -> Bool { - let defaultIsMuted = globalSettings.privateChats.storySettings.mute == .muted +public func resolvedAreStoriesMuted(globalSettings: GlobalNotificationSettingsSet, peer: Peer, peerSettings: TelegramPeerNotificationSettings?, topSearchPeers: [PeerId]) -> Bool { + let defaultIsMuted: Bool + switch globalSettings.privateChats.storySettings.mute { + case .muted: + defaultIsMuted = true + case .unmuted: + defaultIsMuted = false + case .default: + defaultIsMuted = !topSearchPeers.prefix(5).contains(peer.id) + } switch peerSettings?.storySettings.mute { case .none: return defaultIsMuted @@ -95,13 +103,18 @@ func _internal_togglePeerStoriesMuted(account: Account, peerId: PeerId) -> Signa previousSettings = TelegramPeerNotificationSettings.defaultSettings } + var topSearchPeers: [PeerId] = [] + if let value = transaction.retrieveItemCacheEntry(id: cachedRecentPeersEntryId())?.get(CachedRecentPeers.self), value.enabled { + topSearchPeers = value.ids + } + let updatedSettings: TelegramPeerNotificationSettings var storySettings = previousSettings.storySettings switch previousSettings.storySettings.mute { case .default: let globalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) ?? GlobalNotificationSettings.defaultSettings - if resolvedAreStoriesMuted(globalSettings: globalNotificationSettings.effective, peer: peer, peerSettings: previousSettings) { + if resolvedAreStoriesMuted(globalSettings: globalNotificationSettings.effective, peer: peer, peerSettings: previousSettings, topSearchPeers: topSearchPeers) { storySettings.mute = .unmuted } else { storySettings.mute = .muted diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentPeers.swift index 7dd5e98da8..74b375c562 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentPeers.swift @@ -8,7 +8,7 @@ public enum RecentPeers { case disabled } -private func cachedRecentPeersEntryId() -> ItemCacheEntryId { +func cachedRecentPeersEntryId() -> ItemCacheEntryId { return ItemCacheEntryId(collectionId: 101, key: CachedRecentPeers.cacheKey()) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift index 0291b7675d..0097036dcb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift @@ -24,10 +24,22 @@ public extension TelegramEngine { public func requestAccountPrivacySettings() -> Signal { return _internal_requestAccountPrivacySettings(account: self.account) } - + public func updateAccountAutoArchiveChats(value: Bool) -> Signal { return _internal_updateAccountAutoArchiveChats(account: self.account, value: value) } + + public func updateAccountKeepArchivedFolders(value: Bool) -> Signal { + return _internal_updateAccountKeepArchivedFolders(account: self.account, value: value) + } + + public func updateAccountKeepArchivedUnmuted(value: Bool) -> Signal { + return _internal_updateAccountKeepArchivedUnmuted(account: self.account, value: value) + } + + public func updateGlobalPrivacySettings(settings: GlobalPrivacySettings) -> Signal { + return _internal_updateGlobalPrivacySettings(account: self.account, settings: settings) + } public func updateAccountRemovalTimeout(timeout: Int32) -> Signal { return _internal_updateAccountRemovalTimeout(account: self.account, timeout: timeout) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift index af9bda38e6..42e3c22efd 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift @@ -149,14 +149,17 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal AccountPrivacySettings in @@ -170,14 +173,81 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal Signal { + return account.postbox.transaction { transaction -> GlobalPrivacySettings in + return fetchGlobalPrivacySettings(transaction: transaction) + } + |> mapToSignal { settings -> Signal in + var settings = settings + settings.automaticallyArchiveAndMuteNonContacts = value + return _internal_updateGlobalPrivacySettings(account: account, settings: settings) + } +} + +func _internal_updateAccountKeepArchivedFolders(account: Account, value: Bool) -> Signal { + return account.postbox.transaction { transaction -> GlobalPrivacySettings in + return fetchGlobalPrivacySettings(transaction: transaction) + } + |> mapToSignal { settings -> Signal in + var settings = settings + settings.keepArchivedFolders = value + return _internal_updateGlobalPrivacySettings(account: account, settings: settings) + } +} + +func _internal_updateAccountKeepArchivedUnmuted(account: Account, value: Bool) -> Signal { + return account.postbox.transaction { transaction -> GlobalPrivacySettings in + return fetchGlobalPrivacySettings(transaction: transaction) + } + |> mapToSignal { settings -> Signal in + var settings = settings + settings.keepArchivedUnmuted = value + return _internal_updateGlobalPrivacySettings(account: account, settings: settings) + } +} + +func _internal_updateGlobalPrivacySettings(account: Account, settings: GlobalPrivacySettings) -> Signal { + let _ = (account.postbox.transaction { transaction -> Void in + updateGlobalPrivacySettings(transaction: transaction, { _ in + return settings + }) + }).start() + + var flags: Int32 = 0 + if settings.automaticallyArchiveAndMuteNonContacts { + flags |= 1 << 0 + } + if settings.keepArchivedUnmuted { + flags |= 1 << 1 + } + if settings.keepArchivedFolders { + flags |= 1 << 2 + } return account.network.request(Api.functions.account.setGlobalPrivacySettings( - settings: .globalPrivacySettings(flags: 1 << 0, archiveAndMuteNewNoncontactPeers: value ? .boolTrue : .boolFalse) + settings: .globalPrivacySettings(flags: flags) )) |> retryRequest |> ignoreValues diff --git a/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD new file mode 100644 index 0000000000..aa5718c1c1 --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD @@ -0,0 +1,35 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ArchiveInfoScreen", + module_name = "ArchiveInfoScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/Components/SheetComponent", + "//submodules/PresentationDataUtils", + "//submodules/Components/SolidRoundedButtonComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/Markdown", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoContentComponent.swift b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoContentComponent.swift new file mode 100644 index 0000000000..c8c1c83636 --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoContentComponent.swift @@ -0,0 +1,317 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import MultilineTextComponent +import TelegramPresentationData +import AppBundle +import BundleIconComponent +import Markdown +import TelegramCore + +public final class ArchiveInfoContentComponent: Component { + public let theme: PresentationTheme + public let strings: PresentationStrings + public let settings: GlobalPrivacySettings + public let openSettings: () -> Void + + public init( + theme: PresentationTheme, + strings: PresentationStrings, + settings: GlobalPrivacySettings, + openSettings: @escaping () -> Void + ) { + self.theme = theme + self.strings = strings + self.settings = settings + self.openSettings = openSettings + } + + public static func ==(lhs: ArchiveInfoContentComponent, rhs: ArchiveInfoContentComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.settings != rhs.settings { + return false + } + return true + } + + private final class Item { + let icon = ComponentView() + let title = ComponentView() + let text = ComponentView() + + init() { + } + } + + public final class View: UIView { + private let scrollView: UIScrollView + private let iconBackground: UIImageView + private let iconForeground: UIImageView + + private let title = ComponentView() + private let mainText = ComponentView() + + private var items: [Item] = [] + + private var component: ArchiveInfoContentComponent? + + public override init(frame: CGRect) { + self.scrollView = UIScrollView() + + self.iconBackground = UIImageView() + self.iconForeground = UIImageView() + + super.init(frame: frame) + + self.addSubview(self.scrollView) + + self.scrollView.delaysContentTouches = false + self.scrollView.contentInsetAdjustmentBehavior = .never + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.scrollsToTop = false + self.scrollView.clipsToBounds = false + + self.scrollView.addSubview(self.iconBackground) + self.scrollView.addSubview(self.iconForeground) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let result = super.hitTest(point, with: event) { + return result + } else { + return nil + } + } + + func update(component: ArchiveInfoContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let sideInset: CGFloat = 16.0 + let sideIconInset: CGFloat = 40.0 + + var contentHeight: CGFloat = 0.0 + + let iconSize: CGFloat = 90.0 + if self.iconBackground.image == nil { + let backgroundColors = component.theme.chatList.pinnedArchiveAvatarColor.backgroundColors.colors + let colors: NSArray = [backgroundColors.0.cgColor, backgroundColors.1.cgColor] + self.iconBackground.image = generateGradientFilledCircleImage(diameter: iconSize, colors: colors) + } + let iconBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize) * 0.5), y: contentHeight), size: CGSize(width: iconSize, height: iconSize)) + transition.setFrame(view: self.iconBackground, frame: iconBackgroundFrame) + + if self.iconForeground.image == nil { + self.iconForeground.image = generateTintedImage(image: UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon"), color: .white) + } + if let image = self.iconForeground.image { + transition.setFrame(view: self.iconForeground, frame: CGRect(origin: CGPoint(x: iconBackgroundFrame.minX + floor((iconBackgroundFrame.width - image.size.width) * 0.5), y: iconBackgroundFrame.minY + floor((iconBackgroundFrame.height - image.size.height) * 0.5)), size: image.size)) + } + + contentHeight += iconSize + contentHeight += 15.0 + + //TODO:localize + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "This is Your Archive", font: Font.semibold(19.0), textColor: component.theme.list.itemPrimaryTextColor)), + maximumNumberOfLines: 1 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.scrollView.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight), size: titleSize)) + } + contentHeight += titleSize.height + contentHeight += 16.0 + + let text: String + if component.settings.keepArchivedUnmuted { + text = "Archived chats will remain in the Archive when you receive a new message. [Tap to change 〉]()" + } else { + text = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. [Tap to change 〉]()" + } + + //TODO:localize + let mainTextSize = self.mainText.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .markdown(text: text, attributes: MarkdownAttributes( + body: MarkdownAttributeSet( + font: Font.regular(15.0), + textColor: component.theme.list.itemSecondaryTextColor + ), + bold: MarkdownAttributeSet( + font: Font.semibold(15.0), + textColor: component.theme.list.itemSecondaryTextColor + ), + link: MarkdownAttributeSet( + font: Font.regular(15.0), + textColor: component.theme.list.itemAccentColor, + additionalAttributes: [:] + ), + linkAttribute: { attributes in + return ("URL", "") + } + )), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } + }, + tapAction: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + component.openSettings() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + if let mainTextView = self.mainText.view { + if mainTextView.superview == nil { + self.scrollView.addSubview(mainTextView) + } + transition.setFrame(view: mainTextView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - mainTextSize.width) * 0.5), y: contentHeight), size: mainTextSize)) + } + contentHeight += mainTextSize.height + + contentHeight += 24.0 + + struct ItemDesc { + var icon: String + var title: String + var text: String + } + let itemDescs: [ItemDesc] = [ + ItemDesc( + icon: "Chat List/Archive/IconArchived", + title: "Archived Chats", + text: "Move any chat into your Archive and back by swiping on it." + ), + ItemDesc( + icon: "Chat List/Archive/IconHide", + title: "Hiding Archive", + text: "Hide the Archive from your Main screen by swiping on it." + ), + ItemDesc( + icon: "Chat List/Archive/IconStories", + title: "Stories", + text: "Archive Stories from your contacts separately from chats with them." + ) + ] + for i in 0 ..< itemDescs.count { + if i != 0 { + contentHeight += 24.0 + } + + let item: Item + if self.items.count > i { + item = self.items[i] + } else { + item = Item() + self.items.append(item) + } + + let itemDesc = itemDescs[i] + + let iconSize = item.icon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent( + name: itemDesc.icon, + tintColor: component.theme.list.itemAccentColor + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + let titleSize = item.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: itemDesc.title, font: Font.semibold(15.0), textColor: component.theme.list.itemPrimaryTextColor)), + maximumNumberOfLines: 0, + lineSpacing: 0.2 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - sideIconInset, height: 1000.0) + ) + let textSize = item.text.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: itemDesc.text, font: Font.regular(15.0), textColor: component.theme.list.itemSecondaryTextColor)), + maximumNumberOfLines: 0, + lineSpacing: 0.18 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - sideIconInset, height: 1000.0) + ) + + if let iconView = item.icon.view { + if iconView.superview == nil { + self.scrollView.addSubview(iconView) + } + transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 4.0), size: iconSize)) + } + + if let titleView = item.title.view { + if titleView.superview == nil { + self.scrollView.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: sideInset + sideIconInset, y: contentHeight), size: titleSize)) + } + contentHeight += titleSize.height + contentHeight += 2.0 + + if let textView = item.text.view { + if textView.superview == nil { + self.scrollView.addSubview(textView) + } + transition.setFrame(view: textView, frame: CGRect(origin: CGPoint(x: sideInset + sideIconInset, y: contentHeight), size: textSize)) + } + contentHeight += textSize.height + } + + let contentSize = CGSize(width: availableSize.width, height: contentHeight) + let size = CGSize(width: availableSize.width, height: min(availableSize.height, contentSize.height)) + if self.scrollView.bounds.size != size || self.scrollView.contentSize != contentSize { + self.scrollView.frame = CGRect(origin: CGPoint(), size: size) + self.scrollView.contentSize = contentSize + } + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoScreen.swift b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoScreen.swift new file mode 100644 index 0000000000..72057776ed --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoScreen.swift @@ -0,0 +1,279 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import ViewControllerComponent +import AccountContext +import SheetComponent +import ButtonComponent +import TelegramCore + +private final class ArchiveInfoSheetContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let settings: GlobalPrivacySettings + let openSettings: () -> Void + let dismiss: () -> Void + + init( + settings: GlobalPrivacySettings, + openSettings: @escaping () -> Void, + dismiss: @escaping () -> Void + ) { + self.settings = settings + self.openSettings = openSettings + self.dismiss = dismiss + } + + static func ==(lhs: ArchiveInfoSheetContentComponent, rhs: ArchiveInfoSheetContentComponent) -> Bool { + if lhs.settings != rhs.settings { + return false + } + return true + } + + final class View: UIView { + private let content = ComponentView() + private let button = ComponentView() + + private var component: ArchiveInfoSheetContentComponent? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ArchiveInfoSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let environment = environment[EnvironmentType.self].value + + let sideInset: CGFloat = 16.0 + + var contentHeight: CGFloat = 0.0 + contentHeight += 30.0 + + let contentSize = self.content.update( + transition: transition, + component: AnyComponent(ArchiveInfoContentComponent( + theme: environment.theme, + strings: environment.strings, + settings: component.settings, + openSettings: component.openSettings + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) + ) + if let contentView = self.content.view { + if contentView.superview == nil { + self.addSubview(contentView) + } + transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: contentSize)) + } + contentHeight += contentSize.height + contentHeight += 30.0 + + //TODO:localize + let buttonSize = self.button.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + color: environment.theme.list.itemCheckColors.fillColor, + foreground: environment.theme.list.itemCheckColors.foregroundColor, + pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( + Text(text: "Got it", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) + )), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.dismiss() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + ) + let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + transition.setFrame(view: buttonView, frame: buttonFrame) + } + contentHeight += buttonSize.height + + if environment.safeInsets.bottom.isZero { + contentHeight += 16.0 + } else { + contentHeight += environment.safeInsets.bottom + 14.0 + } + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ArchiveInfoScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let settings: GlobalPrivacySettings + + init( + context: AccountContext, + settings: GlobalPrivacySettings + ) { + self.context = context + self.settings = settings + } + + static func ==(lhs: ArchiveInfoScreenComponent, rhs: ArchiveInfoScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.settings != rhs.settings { + return false + } + return true + } + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>() + private let sheetAnimateOut = ActionSlot>() + + private var component: ArchiveInfoScreenComponent? + private var environment: EnvironmentType? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ArchiveInfoScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environment + + let sheetEnvironment = SheetComponentEnvironment( + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { [weak self] _ in + guard let self, let environment = self.environment else { + return + } + self.sheetAnimateOut.invoke(Action { _ in + if let controller = environment.controller() { + controller.dismiss(completion: nil) + } + }) + } + ) + let _ = self.sheet.update( + transition: transition, + component: AnyComponent(SheetComponent( + content: AnyComponent(ArchiveInfoSheetContentComponent( + settings: component.settings, + openSettings: { [weak self] in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + let context = component.context + self.sheetAnimateOut.invoke(Action { [weak context, weak controller] _ in + if let controller, let context { + if let navigationController = controller.navigationController as? NavigationController { + navigationController.pushViewController(context.sharedContext.makeArchiveSettingsController(context: context)) + } + controller.dismiss(completion: nil) + } + }) + }, + dismiss: { [weak self] in + guard let self else { + return + } + self.sheetAnimateOut.invoke(Action { _ in + if let controller = environment.controller() { + controller.dismiss(completion: nil) + } + }) + } + )), + backgroundColor: .color(environment.theme.list.plainBackgroundColor), + animateOut: self.sheetAnimateOut + )), + environment: { + environment + sheetEnvironment + }, + containerSize: availableSize + ) + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public class ArchiveInfoScreen: ViewControllerComponentContainer { + public init(context: AccountContext, settings: GlobalPrivacySettings) { + super.init(context: context, component: ArchiveInfoScreenComponent( + context: context, + settings: settings + ), navigationBarAppearance: .none) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.view.disablesInteractiveModalDismiss = true + } +} diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift index 736fa2ba28..4f96c324d9 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift @@ -286,6 +286,8 @@ public final class AvatarStoryIndicatorComponent: Component { self.indicatorView.image = generateImage(CGSize(width: imageDiameter, height: imageDiameter), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) + context.setLineCap(.round) + var locations: [CGFloat] = [0.0, 1.0] if let counters = component.counters, counters.totalCount > 1 { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index 3654d2c8f7..273cd394b3 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -122,9 +122,9 @@ public final class StoryContentContextImpl: StoryContentContext { if let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView, let cachedUserData = cachedPeerDataView.cachedPeerData as? CachedUserData { var isMuted = false if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings) + isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings, topSearchPeers: []) } else { - isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: nil) + isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: nil, topSearchPeers: []) } additionalPeerData = StoryContentContextState.AdditionalPeerData( isMuted: isMuted, @@ -990,7 +990,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext { return } - let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings()) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: []) let additionalPeerData = StoryContentContextState.AdditionalPeerData( isMuted: isMuted, @@ -1146,7 +1146,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { return } - let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings()) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: []) let additionalPeerData = StoryContentContextState.AdditionalPeerData( isMuted: isMuted, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index cbbf95ed0b..ba79a78216 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -3803,9 +3803,10 @@ public final class StoryItemSetContainerComponent: Component { } let _ = (component.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: component.slice.peer.id), - TelegramEngine.EngineData.Item.NotificationSettings.Global() + TelegramEngine.EngineData.Item.NotificationSettings.Global(), + TelegramEngine.EngineData.Item.Contacts.Top() ) - |> deliverOnMainQueue).start(next: { [weak self] settings, globalSettings in + |> deliverOnMainQueue).start(next: { [weak self] settings, globalSettings, topSearchPeers in guard let self, let component = self.component, let controller = component.controller() else { return } @@ -3855,7 +3856,7 @@ public final class StoryItemSetContainerComponent: Component { }))) } - let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings()) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings(), topSearchPeers: topSearchPeers) items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: component.slice.additionalPeerData.isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/Contents.json index 38f0c81fc2..6e965652df 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/Contents.json @@ -1,9 +1,9 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "provides-namespace" : true } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArchived.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArchived.imageset/Contents.json new file mode 100644 index 0000000000..542d8de3b3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArchived.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "archivedown_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArchived.imageset/archivedown_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArchived.imageset/archivedown_30.pdf new file mode 100644 index 0000000000..3bd39cf7ae --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconArchived.imageset/archivedown_30.pdf @@ -0,0 +1,158 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.334961 4.839722 cm +1.000000 1.000000 1.000000 scn +3.165036 20.330225 m +3.096358 20.330244 l +2.698711 20.330404 2.404061 20.330524 2.145121 20.279018 c +1.087940 20.068731 0.261530 19.242321 0.051243 18.185141 c +-0.000263 17.926201 -0.000144 17.631550 0.000018 17.233904 c +0.000036 17.165226 l +0.000018 17.096546 l +-0.000144 16.698900 -0.000263 16.404249 0.051243 16.145309 c +0.176384 15.516185 0.519719 14.968784 1.000036 14.584322 c +1.000036 7.065225 l +1.000036 7.035685 l +1.000031 5.940598 1.000028 5.078021 1.056741 4.383881 c +1.114598 3.675747 1.234776 3.084553 1.508491 2.547359 c +1.955740 1.669582 2.669394 0.955929 3.547170 0.508678 c +4.084365 0.234966 4.675559 0.114788 5.383692 0.056931 c +6.077805 0.000217 6.940342 0.000221 8.035376 0.000225 c +8.035421 0.000225 l +8.035467 0.000225 l +8.035513 0.000225 l +8.065035 0.000225 l +13.265038 0.000225 l +13.294560 0.000225 l +13.294606 0.000225 l +13.294652 0.000225 l +13.294698 0.000225 l +14.389733 0.000221 15.252270 0.000217 15.946386 0.056931 c +16.654520 0.114788 17.245716 0.234966 17.782909 0.508680 c +18.660686 0.955929 19.374340 1.669584 19.821590 2.547361 c +20.095303 3.084557 20.215481 3.675751 20.273338 4.383883 c +20.330051 5.078045 20.330048 5.940656 20.330040 7.035792 c +20.330040 7.065230 l +20.330036 14.584322 l +20.810352 14.968784 21.153688 15.516184 21.278828 16.145309 c +21.330338 16.404255 21.330219 16.698914 21.330055 17.096573 c +21.330036 17.165226 l +21.330055 17.233877 l +21.330219 17.631538 21.330338 17.926195 21.278828 18.185141 c +21.068542 19.242321 20.242134 20.068731 19.184952 20.279018 c +18.926010 20.330524 18.631363 20.330404 18.233715 20.330244 c +18.165035 20.330225 l +3.165036 20.330225 l +h +19.000036 14.023484 m +18.785206 13.999983 18.540018 14.000082 18.233719 14.000207 c +18.165035 14.000225 l +3.165036 14.000225 l +3.096356 14.000207 l +2.790055 14.000082 2.544866 13.999983 2.330036 14.023484 c +2.330036 7.065225 l +2.330036 5.934147 2.330554 5.125833 2.382324 4.492185 c +2.433461 3.866301 2.531270 3.469620 2.693530 3.151167 c +3.013267 2.523647 3.523457 2.013456 4.150978 1.693718 c +4.469430 1.531460 4.866112 1.433651 5.491996 1.382513 c +6.125643 1.330742 6.933958 1.330225 8.065035 1.330225 c +13.265038 1.330225 l +14.396117 1.330225 15.204433 1.330742 15.838082 1.382513 c +16.463966 1.433651 16.860649 1.531460 17.179102 1.693718 c +17.806622 2.013456 18.316814 2.523647 18.636551 3.151169 c +18.798809 3.469620 18.896618 3.866302 18.947754 4.492188 c +18.999527 5.125835 19.000042 5.934151 19.000042 7.065229 c +19.000036 14.023484 l +h +2.404591 18.974573 m +2.517357 18.997004 2.668507 19.000225 3.165036 19.000225 c +18.165035 19.000225 l +18.661564 19.000225 18.812714 18.997004 18.925480 18.974573 c +19.455063 18.869232 19.869045 18.455252 19.974384 17.925671 c +19.996815 17.812904 20.000034 17.661755 20.000034 17.165226 c +20.000034 16.668695 19.996815 16.517546 19.974384 16.404779 c +19.869045 15.875197 19.455063 15.461217 18.925480 15.355877 c +18.812714 15.333447 18.661564 15.330225 18.165035 15.330225 c +3.165036 15.330225 l +2.668507 15.330225 2.517357 15.333447 2.404591 15.355877 c +1.875009 15.461217 1.461028 15.875197 1.355688 16.404779 c +1.333257 16.517546 1.330036 16.668695 1.330036 17.165226 c +1.330036 17.661755 1.333257 17.812904 1.355688 17.925671 c +1.461028 18.455252 1.875009 18.869232 2.404591 18.974573 c +h +10.665036 11.825281 m +11.032306 11.825281 11.330036 11.527551 11.330036 11.160281 c +11.330036 5.765734 l +14.194810 8.630508 l +14.454509 8.890207 14.875564 8.890207 15.135262 8.630508 c +15.394960 8.370810 15.394960 7.949755 15.135262 7.690056 c +11.135262 3.690056 l +10.875564 3.430357 10.454509 3.430357 10.194811 3.690056 c +6.194810 7.690056 l +5.935111 7.949755 5.935111 8.370810 6.194810 8.630508 c +6.454509 8.890207 6.875564 8.890207 7.135262 8.630508 c +10.000036 5.765734 l +10.000036 11.160281 l +10.000036 11.527551 10.297767 11.825281 10.665036 11.825281 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 3953 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000004043 00000 n +0000004066 00000 n +0000004239 00000 n +0000004313 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +4372 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconHide.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconHide.imageset/Contents.json new file mode 100644 index 0000000000..4cf066fe19 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconHide.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "eyecrossed_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconHide.imageset/eyecrossed_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconHide.imageset/eyecrossed_30.pdf new file mode 100644 index 0000000000..793525ed08 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconHide.imageset/eyecrossed_30.pdf @@ -0,0 +1,117 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.602539 4.205200 cm +1.000000 1.000000 1.000000 scn +1.867687 21.265064 m +1.607988 21.524763 1.186934 21.524763 0.927235 21.265064 c +0.667536 21.005365 0.667536 20.584312 0.927235 20.324614 c +20.927235 0.324612 l +21.186934 0.064915 21.607988 0.064915 21.867687 0.324612 c +22.127386 0.584312 22.127386 1.005367 21.867687 1.265064 c +17.973269 5.159483 l +20.245113 6.676920 21.723158 8.676188 22.439053 9.801323 c +22.931236 10.574864 22.913918 11.552846 22.384398 12.306744 c +21.070900 14.176826 17.414938 18.459877 11.397670 18.459877 c +9.295700 18.459877 7.481872 17.937233 5.955382 17.177368 c +1.867687 21.265064 l +h +6.955018 16.177734 m +8.841949 14.290803 l +9.558202 14.815262 10.441629 15.124917 11.397383 15.124917 c +13.788776 15.124917 15.727383 13.186310 15.727383 10.794917 c +15.727383 9.839164 15.417727 8.955734 14.893269 8.239483 c +17.011539 6.121212 l +19.175356 7.494809 20.604380 9.395405 21.316936 10.515294 c +21.522497 10.838364 21.513580 11.232577 21.296036 11.542304 c +20.050093 13.316206 16.740896 17.129879 11.397670 17.129879 c +9.714686 17.129879 8.233499 16.751526 6.955018 16.177734 c +h +13.936440 9.196312 m +14.228426 9.659100 14.397383 10.207277 14.397383 10.794917 c +14.397383 12.451771 13.054237 13.794917 11.397383 13.794917 c +10.809743 13.794917 10.261566 13.625959 9.798779 13.333974 c +13.936440 9.196312 l +h +3.867007 15.884836 m +2.185803 14.625908 1.034453 13.194464 0.410942 12.306746 c +-0.118577 11.552848 -0.135898 10.574865 0.356286 9.801323 c +1.616695 7.820396 5.239589 3.129879 11.397670 3.129879 c +13.032294 3.129879 14.488289 3.460373 15.768883 3.982960 c +14.735173 5.016670 l +13.730924 4.669720 12.618762 4.459877 11.397670 4.459877 c +5.965021 4.459877 2.688515 8.613417 1.478402 10.515295 c +1.272843 10.838363 1.281759 11.232578 1.499304 11.542305 c +2.110389 12.412331 3.217787 13.773022 4.818052 14.933791 c +3.867007 15.884836 l +h +7.368151 12.383697 m +7.174029 11.891793 7.067383 11.355809 7.067383 10.794917 c +7.067383 8.403524 9.005990 6.464917 11.397383 6.464917 c +11.958275 6.464917 12.494259 6.571564 12.986162 6.765685 c +11.912820 7.839027 l +11.745357 7.810030 11.573134 7.794917 11.397383 7.794917 c +9.740528 7.794917 8.397383 9.138063 8.397383 10.794917 c +8.397383 10.970669 8.412496 11.142891 8.441493 11.310354 c +7.368151 12.383697 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2357 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002447 00000 n +0000002470 00000 n +0000002643 00000 n +0000002717 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2776 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconStories.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconStories.imageset/Contents.json new file mode 100644 index 0000000000..ac64efccdb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconStories.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stories_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconStories.imageset/stories_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconStories.imageset/stories_30.pdf new file mode 100644 index 0000000000..41fcea74c6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Archive/IconStories.imageset/stories_30.pdf @@ -0,0 +1,142 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.334961 4.335022 cm +1.000000 1.000000 1.000000 scn +10.665000 20.000017 m +10.564305 20.000017 10.463983 19.998423 10.364057 19.995258 c +5.453339 19.839729 1.499571 15.891432 1.335311 10.982922 c +1.331780 10.877388 1.330000 10.771411 1.330000 10.665017 c +1.330000 10.564322 1.331594 10.464000 1.334759 10.364074 c +1.488760 5.501557 5.361441 1.577299 10.202835 1.341259 c +10.355950 1.333794 10.510034 1.330017 10.665000 1.330017 c +10.939749 1.330017 11.211570 1.341869 11.480000 1.365063 c +11.845905 1.396679 12.168161 1.125687 12.199779 0.759781 c +12.231396 0.393875 11.960401 0.071617 11.594495 0.040001 c +11.288037 0.013519 10.978016 0.000015 10.665000 0.000015 c +10.503942 0.000015 10.343718 0.003586 10.184402 0.010651 c +4.517327 0.261976 0.000000 4.935957 0.000000 10.665017 c +0.000000 16.555134 4.774883 21.330017 10.665000 21.330017 c +10.978016 21.330017 11.288037 21.316515 11.594495 21.290033 c +11.960401 21.258417 12.231396 20.936161 12.199779 20.570255 c +12.168161 20.204350 11.845905 19.933353 11.480000 19.964972 c +11.211570 19.988165 10.939749 20.000017 10.665000 20.000017 c +h +14.289712 20.011681 m +14.445146 20.344437 14.840902 20.488184 15.173659 20.332750 c +15.738993 20.068678 16.277210 19.756557 16.783070 19.401701 c +17.083740 19.190784 17.156498 18.776064 16.945583 18.475395 c +16.734665 18.174728 16.319946 18.101969 16.019278 18.312885 c +15.576334 18.623606 15.105290 18.896744 14.610783 19.127731 c +14.278027 19.283165 14.134277 19.678923 14.289712 20.011681 c +h +18.475376 16.945601 m +18.776047 17.156517 19.190765 17.083757 19.401684 16.783089 c +19.756538 16.277227 20.068661 15.739011 20.332733 15.173677 c +20.488167 14.840920 20.344418 14.445164 20.011662 14.289730 c +19.678905 14.134295 19.283150 14.278044 19.127716 14.610801 c +18.896725 15.105309 18.623589 15.576352 18.312866 16.019297 c +18.101952 16.319963 18.174709 16.734684 18.475376 16.945601 c +h +20.570236 12.199796 m +20.936142 12.231413 21.258400 11.960418 21.290016 11.594512 c +21.316498 11.288054 21.330002 10.978033 21.330002 10.665017 c +21.330002 10.352001 21.316498 10.041980 21.290016 9.735522 c +21.258400 9.369617 20.936142 9.098621 20.570236 9.130239 c +20.204330 9.161856 19.933338 9.484112 19.964954 9.850018 c +19.988148 10.118447 20.000000 10.390268 20.000000 10.665017 c +20.000000 10.939766 19.988148 11.211587 19.964954 11.480017 c +19.933338 11.845922 20.204330 12.168179 20.570236 12.199796 c +h +20.011662 7.040305 m +20.344418 6.884871 20.488167 6.489115 20.332733 6.156358 c +20.068661 5.591024 19.756542 5.052807 19.401684 4.546947 c +19.190765 4.246277 18.776047 4.173519 18.475380 4.384434 c +18.174709 4.595352 18.101952 5.010071 18.312866 5.310740 c +18.623589 5.753683 18.896725 6.224727 19.127716 6.719234 c +19.283150 7.051991 19.678905 7.195740 20.011662 7.040305 c +h +16.945583 2.854641 m +17.156498 2.553970 17.083740 2.139252 16.783073 1.928333 c +16.277210 1.573479 15.738994 1.261356 15.173660 0.997284 c +14.840903 0.841850 14.445147 0.985600 14.289713 1.318356 c +14.134278 1.651112 14.278027 2.046867 14.610784 2.202301 c +15.105291 2.433292 15.576335 2.706429 16.019279 3.017151 c +16.319946 3.228065 16.734669 3.155308 16.945583 2.854641 c +h +10.047509 15.272626 m +8.938539 15.965733 7.500061 15.168457 7.500061 13.860708 c +7.500061 7.469204 l +7.500061 6.161454 8.938538 5.364180 10.047508 6.057286 c +15.160713 9.253038 l +16.204113 9.905165 16.204117 11.424746 15.160714 12.076873 c +10.047509 15.272626 l +h +8.830061 13.860708 m +8.830061 14.123829 9.119485 14.284241 9.342610 14.144788 c +14.455815 10.949035 l +14.665749 10.817827 14.665750 10.512086 14.455814 10.380877 c +9.342610 7.185124 l +9.119485 7.045671 8.830061 7.206082 8.830061 7.469204 c +8.830061 13.860708 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 3751 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003841 00000 n +0000003864 00000 n +0000004037 00000 n +0000004111 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +4170 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/New Folder/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/New Folder/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/New Folder/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index ad0c4f1b99..bf3d3bbc3d 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -206,6 +206,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.mainContainerNode = ChatListContainerNode(context: context, controller: nil, location: chatListLocation, chatListMode: chatListMode, previewing: false, controlsHistoryPreload: false, isInlineMode: false, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { _ in }, secondaryEmptyAction: { + }, openArchiveSettings: { }) self.chatListNode = nil } else { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index df4de04972..75829a7fc1 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1728,6 +1728,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { return PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: isArchive ? .archive : .saved) } + public func makeArchiveSettingsController(context: AccountContext) -> ViewController { + return archiveSettingsController(context: context) + } + public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController { let mappedSource: PremiumSource switch source { diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index 1b50d6b591..9c34660759 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -42,6 +42,7 @@ public enum UndoOverlayContent { case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?) case notificationSoundAdded(title: String, text: String, action: (() -> Void)?) case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?, timeout: Double?) + case premiumPaywall(title: String?, text: String, customUndoText: String?, timeout: Double?, linkAction: ((String) -> Void)?) case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?) } diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 2d9ae2baef..e525d09eac 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -845,7 +845,6 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.animatedStickerNode = nil self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) - let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) @@ -912,6 +911,69 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.textNode.maximumNumberOfLines = 5 + if let customUndoText = customUndoText { + undoText = customUndoText + displayUndo = true + } else { + displayUndo = false + } + case let .premiumPaywall(title, text, customUndoText, timeout, linkAction): + self.avatarNode = nil + self.iconNode = ASImageNode() + self.iconNode?.displayWithoutProcessing = true + self.iconNode?.displaysAsynchronously = false + self.iconNode?.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/PremiumIcon"), color: .white) + self.iconCheckNode = nil + self.animationNode = nil + self.animatedStickerNode = nil + + if let title = title, text.isEmpty { + self.titleNode.attributedText = nil + let body = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) + let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) + let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor) + let attributedText = parseMarkdownIntoAttributedString(title, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in + return ("URL", contents) + }), textAlignment: .natural) + self.textNode.attributedText = attributedText + } else { + if let title = title { + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + } else { + self.titleNode.attributedText = nil + } + + let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) + let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) + let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in + return ("URL", contents) + }), textAlignment: .natural) + self.textNode.attributedText = attributedText + } + + if let linkAction { + self.textNode.highlightAttributeAction = { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } + } + self.textNode.tapAttributeAction = { attributes, _ in + if let value = attributes[NSAttributedString.Key(rawValue: "URL")] as? String { + linkAction(value) + } + } + } + + if text.contains("](") { + isUserInteractionEnabled = true + } + self.originalRemainingSeconds = timeout ?? (isUserInteractionEnabled ? 5 : 3) + + self.textNode.maximumNumberOfLines = 5 + if let customUndoText = customUndoText { undoText = customUndoText displayUndo = true @@ -1020,7 +1082,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { switch content { case .removedChat: self.panelWrapperNode.addSubnode(self.timerTextNode) - case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .peers: + case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .premiumPaywall, .peers: if self.textNode.tapAttributeAction != nil || displayUndo { self.isUserInteractionEnabled = true } else {