Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-07-13 23:18:35 +02:00
commit 0b7869688d
42 changed files with 2064 additions and 116 deletions

View File

@ -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<Never, NoError>

View File

@ -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",

View File

@ -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)
}

View File

@ -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
}

View File

@ -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<Empty>?
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<Empty>
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: size.height - 41.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
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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"
}
}
}

View File

@ -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<Void, NoError> 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<Void, NoError> 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<Void, NoError> 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<Void, NoError> 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<Void, NoError> 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<Void, NoError> 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<Void, NoError> 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<Void, NoError> 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<Void, NoError> 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<Void, NoError> 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<Void, NoError> 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()
}

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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)
})
}

View File

@ -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 {

View File

@ -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
}
}
}
}

View File

@ -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<EnginePeer.Id>
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 []
}
}
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -8,7 +8,7 @@ public enum RecentPeers {
case disabled
}
private func cachedRecentPeersEntryId() -> ItemCacheEntryId {
func cachedRecentPeersEntryId() -> ItemCacheEntryId {
return ItemCacheEntryId(collectionId: 101, key: CachedRecentPeers.cacheKey())
}

View File

@ -24,10 +24,22 @@ public extension TelegramEngine {
public func requestAccountPrivacySettings() -> Signal<AccountPrivacySettings, NoError> {
return _internal_requestAccountPrivacySettings(account: self.account)
}
public func updateAccountAutoArchiveChats(value: Bool) -> Signal<Never, NoError> {
return _internal_updateAccountAutoArchiveChats(account: self.account, value: value)
}
public func updateAccountKeepArchivedFolders(value: Bool) -> Signal<Never, NoError> {
return _internal_updateAccountKeepArchivedFolders(account: self.account, value: value)
}
public func updateAccountKeepArchivedUnmuted(value: Bool) -> Signal<Never, NoError> {
return _internal_updateAccountKeepArchivedUnmuted(account: self.account, value: value)
}
public func updateGlobalPrivacySettings(settings: GlobalPrivacySettings) -> Signal<Never, NoError> {
return _internal_updateGlobalPrivacySettings(account: self.account, settings: settings)
}
public func updateAccountRemovalTimeout(timeout: Int32) -> Signal<Void, NoError> {
return _internal_updateAccountRemovalTimeout(account: self.account, timeout: timeout)

View File

@ -149,14 +149,17 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal<Account
peerMap[peer.peer.id] = peer
}
let automaticallyArchiveAndMuteNonContacts: Bool
let globalSettings: GlobalPrivacySettings
switch globalPrivacySettings {
case let .globalPrivacySettings(_, archiveAndMuteNewNoncontactPeers):
if let archiveAndMuteNewNoncontactPeers = archiveAndMuteNewNoncontactPeers {
automaticallyArchiveAndMuteNonContacts = archiveAndMuteNewNoncontactPeers == .boolTrue
} else {
automaticallyArchiveAndMuteNonContacts = false
}
case let .globalPrivacySettings(flags):
let automaticallyArchiveAndMuteNonContacts = (flags & (1 << 0)) != 0
let keepArchivedUnmuted = (flags & (1 << 1)) != 0
let keepArchivedFolders = (flags & (1 << 2)) != 0
globalSettings = GlobalPrivacySettings(
automaticallyArchiveAndMuteNonContacts: automaticallyArchiveAndMuteNonContacts,
keepArchivedUnmuted: keepArchivedUnmuted,
keepArchivedFolders: keepArchivedFolders
)
}
return account.postbox.transaction { transaction -> AccountPrivacySettings in
@ -170,14 +173,81 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal<Account
return settings
})
return AccountPrivacySettings(presence: SelectivePrivacySettings(apiRules: lastSeenRules, peers: peerMap), groupInvitations: SelectivePrivacySettings(apiRules: groupRules, peers: peerMap), voiceCalls: SelectivePrivacySettings(apiRules: voiceRules, peers: peerMap), voiceCallsP2P: SelectivePrivacySettings(apiRules: voiceP2PRules, peers: peerMap), profilePhoto: SelectivePrivacySettings(apiRules: profilePhotoRules, peers: peerMap), forwards: SelectivePrivacySettings(apiRules: forwardRules, peers: peerMap), phoneNumber: SelectivePrivacySettings(apiRules: phoneNumberRules, peers: peerMap), phoneDiscoveryEnabled: phoneDiscoveryValue, voiceMessages: SelectivePrivacySettings(apiRules: voiceMessagesRules, peers: peerMap), bio: SelectivePrivacySettings(apiRules: bioRules, peers: peerMap), automaticallyArchiveAndMuteNonContacts: automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: accountTimeoutSeconds, messageAutoremoveTimeout: messageAutoremoveSeconds)
updateGlobalPrivacySettings(transaction: transaction, { _ in
return globalSettings
})
return AccountPrivacySettings(
presence: SelectivePrivacySettings(apiRules: lastSeenRules, peers: peerMap),
groupInvitations: SelectivePrivacySettings(apiRules: groupRules, peers: peerMap),
voiceCalls: SelectivePrivacySettings(apiRules: voiceRules, peers: peerMap),
voiceCallsP2P: SelectivePrivacySettings(apiRules: voiceP2PRules, peers: peerMap),
profilePhoto: SelectivePrivacySettings(apiRules: profilePhotoRules, peers: peerMap),
forwards: SelectivePrivacySettings(apiRules: forwardRules, peers: peerMap),
phoneNumber: SelectivePrivacySettings(apiRules: phoneNumberRules, peers: peerMap),
phoneDiscoveryEnabled: phoneDiscoveryValue,
voiceMessages: SelectivePrivacySettings(apiRules: voiceMessagesRules, peers: peerMap),
bio: SelectivePrivacySettings(apiRules: bioRules, peers: peerMap),
globalSettings: globalSettings,
accountRemovalTimeout: accountTimeoutSeconds,
messageAutoremoveTimeout: messageAutoremoveSeconds
)
}
}
}
func _internal_updateAccountAutoArchiveChats(account: Account, value: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> GlobalPrivacySettings in
return fetchGlobalPrivacySettings(transaction: transaction)
}
|> mapToSignal { settings -> Signal<Never, NoError> in
var settings = settings
settings.automaticallyArchiveAndMuteNonContacts = value
return _internal_updateGlobalPrivacySettings(account: account, settings: settings)
}
}
func _internal_updateAccountKeepArchivedFolders(account: Account, value: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> GlobalPrivacySettings in
return fetchGlobalPrivacySettings(transaction: transaction)
}
|> mapToSignal { settings -> Signal<Never, NoError> in
var settings = settings
settings.keepArchivedFolders = value
return _internal_updateGlobalPrivacySettings(account: account, settings: settings)
}
}
func _internal_updateAccountKeepArchivedUnmuted(account: Account, value: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> GlobalPrivacySettings in
return fetchGlobalPrivacySettings(transaction: transaction)
}
|> mapToSignal { settings -> Signal<Never, NoError> in
var settings = settings
settings.keepArchivedUnmuted = value
return _internal_updateGlobalPrivacySettings(account: account, settings: settings)
}
}
func _internal_updateGlobalPrivacySettings(account: Account, settings: GlobalPrivacySettings) -> Signal<Never, NoError> {
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

View File

@ -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",
],
)

View File

@ -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<Empty>()
let title = ComponentView<Empty>()
let text = ComponentView<Empty>()
init() {
}
}
public final class View: UIView {
private let scrollView: UIScrollView
private let iconBackground: UIImageView
private let iconForeground: UIImageView
private let title = ComponentView<Empty>()
private let mainText = ComponentView<Empty>()
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<Empty>, 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<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -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<Empty>()
private let button = ComponentView<Empty>()
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<EnvironmentType>, 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<EnvironmentType>, 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<Action<Void>>()
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<ViewControllerComponentContainer.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<ViewControllerComponentContainer.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
}
}

View File

@ -286,11 +286,13 @@ 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 {
let center = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
let spacing: CGFloat = 2.0
let spacing: CGFloat = component.activeLineWidth * 2.0
let angularSpacing: CGFloat = spacing / radius
let circleLength = CGFloat.pi * 2.0 * radius
let segmentLength = (circleLength - spacing * CGFloat(counters.totalCount)) / CGFloat(counters.totalCount)

View File

@ -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,

View File

@ -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)

View File

@ -1,9 +1,9 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "archivedown_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "eyecrossed_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "stories_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -309,7 +309,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
let imageSide: CGFloat
imageSide = titleLayout.size.height + textLayout.size.height - 13.0
imageSide = titleLayout.size.height + textLayout.size.height - 12.0
var applyImage: (() -> TransformImageNode)?
if let imageDimensions = imageDimensions {

View File

@ -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 {

View File

@ -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 {

View File

@ -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?)
}

View File

@ -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 {

View File

@ -5,9 +5,135 @@ import MediaPlayer
import LegacyComponents
public class VolumeButtonsListener: NSObject {
private let handler: PGCameraVolumeButtonHandler
public class VolumeButtonsListener {
private final class ListenerReference {
let id: Int
weak var listener: VolumeButtonsListener?
init(id: Int, listener: VolumeButtonsListener) {
self.id = id
self.listener = listener
}
}
private enum Action {
case up
case upRelease
case down
case downRelease
}
private final class SharedContext: NSObject {
private var handler: PGCameraVolumeButtonHandler?
private var nextListenerId: Int = 0
private var listeners: [ListenerReference] = []
override init() {
super.init()
/*self.disposable = (shouldBeActive
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.handler.enabled = value
})*/
}
deinit {
if let handler = self.handler {
handler.enabled = false
}
}
func add(listener: VolumeButtonsListener) -> Int {
let id = self.nextListenerId
self.nextListenerId += 1
self.listeners.append(ListenerReference(id: id, listener: listener))
self.updateListeners()
return id
}
func update(id: Int) {
self.updateListeners()
}
func remove(id: Int) {
if let index = self.listeners.firstIndex(where: { $0.id == id }) {
self.listeners.remove(at: index)
self.updateListeners()
}
}
private func performAction(action: Action) {
for i in (0 ..< self.listeners.count).reversed() {
if let listener = self.listeners[i].listener, listener.isActive {
switch action {
case .up:
listener.upPressed()
case .upRelease:
listener.upReleased()
case .down:
listener.downPressed()
case .downRelease:
listener.downReleased()
}
}
}
}
private func updateListeners() {
var isActive = false
for i in (0 ..< self.listeners.count).reversed() {
if let listener = self.listeners[i].listener {
if listener.isActive {
isActive = true
}
} else {
self.listeners.remove(at: i)
}
}
if isActive {
if self.handler == nil {
self.handler = PGCameraVolumeButtonHandler(upButtonPressedBlock: { [weak self] in
self?.performAction(action: .up)
}, upButtonReleasedBlock: { [weak self] in
self?.performAction(action: .upRelease)
}, downButtonPressedBlock: { [weak self] in
self?.performAction(action: .down)
}, downButtonReleasedBlock: { [weak self] in
self?.performAction(action: .downRelease)
})
}
self.handler?.enabled = true
} else {
if let handler = self.handler {
self.handler = nil
handler.enabled = false
}
}
}
}
private static var sharedContext: SharedContext = {
return SharedContext()
}()
fileprivate let upPressed: () -> Void
fileprivate let upReleased: () -> Void
fileprivate let downPressed: () -> Void
fileprivate let downReleased: () -> Void
private var index: Int?
fileprivate var isActive: Bool = false
private var disposable: Disposable?
public init(
@ -17,29 +143,28 @@ public class VolumeButtonsListener: NSObject {
downPressed: @escaping () -> Void,
downReleased: @escaping () -> Void = {}
) {
self.handler = PGCameraVolumeButtonHandler(upButtonPressedBlock: {
upPressed()
}, upButtonReleasedBlock: {
upReleased()
}, downButtonPressedBlock: {
downPressed()
}, downButtonReleasedBlock: {
downReleased()
})
self.upPressed = upPressed
self.upReleased = upReleased
self.downPressed = downPressed
self.downReleased = downReleased
super.init()
self.index = VolumeButtonsListener.sharedContext.add(listener: self)
self.disposable = (shouldBeActive
|> distinctUntilChanged
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
guard let self, let index = self.index else {
return
}
strongSelf.handler.enabled = value
self.isActive = value
VolumeButtonsListener.sharedContext.update(id: index)
})
}
deinit {
self.handler.enabled = false
if let index = self.index {
VolumeButtonsListener.sharedContext.remove(id: index)
}
self.disposable?.dispose()
}
}