From c6cf803dc542d6dcf7ed2fb0259909fb281029bf Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 10 Sep 2018 18:00:01 +0300 Subject: [PATCH] no message --- TelegramUI.xcodeproj/project.pbxproj | 4 + ...uthorizationSequenceSignUpController.swift | 44 ++--------- ...rizationSequenceSignUpControllerNode.swift | 12 ++- .../ChannelMembersSearchContainerNode.swift | 18 ++--- .../ChannelMembersSearchController.swift | 2 +- .../ChannelMembersSearchControllerNode.swift | 14 ++-- TelegramUI/ChatListController.swift | 4 +- TelegramUI/ChatListControllerNode.swift | 8 +- TelegramUI/ChatListEmptyNode.swift | 2 +- TelegramUI/ChatListNode.swift | 12 +-- TelegramUI/ChatListPresentationData.swift | 6 +- TelegramUI/ChatListSearchContainerNode.swift | 72 +++++++++--------- TelegramUI/ComposeControllerNode.swift | 2 +- TelegramUI/ContactListNode.swift | 76 +++++++++++-------- .../ContactMultiselectionControllerNode.swift | 2 +- .../ContactSelectionControllerNode.swift | 5 +- TelegramUI/ContactsPeerItem.swift | 10 ++- TelegramUI/ContactsSearchContainerNode.swift | 44 ++++------- TelegramUI/CreatePasswordController.swift | 5 +- TelegramUI/GroupInfoController.swift | 2 + TelegramUI/HashtagSearchController.swift | 2 +- TelegramUI/InviteContactsController.swift | 2 +- TelegramUI/InviteContactsControllerNode.swift | 36 +++++---- TelegramUI/ItemListController.swift | 8 +- TelegramUI/LegacyAvatarPicker.swift | 37 +++++++++ TelegramUI/OngoingCallThreadLocalContext.mm | 17 +++-- TelegramUI/PeerSelectionControllerNode.swift | 6 +- TelegramUI/PresentationData.swift | 57 +++++++++++++- TelegramUI/ResetPasswordController.swift | 5 +- ...ecureIdAuthPasswordOptionContentNode.swift | 4 +- TelegramUI/SettingsController.swift | 27 +++++-- .../TelegramInitializeLegacyComponents.swift | 38 ++++++---- .../ThemeAutoNightSettingsController.swift | 8 +- .../TwoStepVerificationUnlockController.swift | 5 +- TelegramUI/UserInfoController.swift | 4 +- TelegramUI/UsernameSetupController.swift | 50 +++++++++--- 36 files changed, 403 insertions(+), 247 deletions(-) create mode 100644 TelegramUI/LegacyAvatarPicker.swift diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 28b7eb9dfa..c60bd46dd3 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -189,6 +189,7 @@ D0642EFC1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0642EFB1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift */; }; D064EF871F69A06F00AC0398 /* MessageContentKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = D064EF861F69A06F00AC0398 /* MessageContentKind.swift */; }; D0671F232143BDA6000A8AE7 /* TwoStepVerificationEmptyItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0671F222143BDA6000A8AE7 /* TwoStepVerificationEmptyItem.swift */; }; + D0671F2D2145AB28000A8AE7 /* LegacyAvatarPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0671F2C2145AB28000A8AE7 /* LegacyAvatarPicker.swift */; }; D067B4A5211C911C00796039 /* LegacyChannelIntroController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D067B4A4211C911C00796039 /* LegacyChannelIntroController.swift */; }; D067B4AA211C916300796039 /* TGChannelIntroController.h in Headers */ = {isa = PBXBuildFile; fileRef = D067B4A6211C916200796039 /* TGChannelIntroController.h */; }; D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */ = {isa = PBXBuildFile; fileRef = D067B4A9211C916200796039 /* TGChannelIntroController.m */; }; @@ -1441,6 +1442,7 @@ D0642EFB1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryNavigationButtons.swift; sourceTree = ""; }; D064EF861F69A06F00AC0398 /* MessageContentKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContentKind.swift; sourceTree = ""; }; D0671F222143BDA6000A8AE7 /* TwoStepVerificationEmptyItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoStepVerificationEmptyItem.swift; sourceTree = ""; }; + D0671F2C2145AB28000A8AE7 /* LegacyAvatarPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyAvatarPicker.swift; sourceTree = ""; }; D067B4A4211C911C00796039 /* LegacyChannelIntroController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyChannelIntroController.swift; sourceTree = ""; }; D067B4A6211C916200796039 /* TGChannelIntroController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGChannelIntroController.h; sourceTree = ""; }; D067B4A9211C916200796039 /* TGChannelIntroController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGChannelIntroController.m; sourceTree = ""; }; @@ -2869,6 +2871,7 @@ D023ED2D1DDB5BEC00BD496D /* LegacyAttachmentMenu.swift */, D0119CCF20CAE75F00895300 /* LegacySecureIdAttachmentMenu.swift */, D023EBB11DDA800700BD496D /* LegacyMediaPickers.swift */, + D0671F2C2145AB28000A8AE7 /* LegacyAvatarPicker.swift */, D00E15251DDBD4E700ACF65C /* LegacyCamera.swift */, D023ED2F1DDB605D00BD496D /* LegacyEmptyController.swift */, D023ED311DDB60CF00BD496D /* LegacyNavigationController.swift */, @@ -4973,6 +4976,7 @@ D0DE66061F9A51E200EF4AE9 /* GalleryHiddenMediaManager.swift in Sources */, D0EC6D5C1EB9F58800EBF1C3 /* ListMessageFileItemNode.swift in Sources */, D0471B561EFDB40F0074D609 /* BotCheckoutActionButton.swift in Sources */, + D0671F2D2145AB28000A8AE7 /* LegacyAvatarPicker.swift in Sources */, D0EC6D5D1EB9F58800EBF1C3 /* ListMessageSnippetItemNode.swift in Sources */, D0EC6D5E1EB9F58800EBF1C3 /* ListMessageHoleItem.swift in Sources */, D0EC6D5F1EB9F58800EBF1C3 /* GridMessageItem.swift in Sources */, diff --git a/TelegramUI/AuthorizationSequenceSignUpController.swift b/TelegramUI/AuthorizationSequenceSignUpController.swift index 4c85b69bfe..c92eee5567 100644 --- a/TelegramUI/AuthorizationSequenceSignUpController.swift +++ b/TelegramUI/AuthorizationSequenceSignUpController.swift @@ -48,45 +48,15 @@ final class AuthorizationSequenceSignUpController: ViewController { } override public func loadDisplayNode() { - let currentAvatarMixin = Atomic(value: nil) + let currentAvatarMixin = Atomic(value: nil) self.displayNode = AuthorizationSequenceSignUpControllerNode(theme: self.theme, strings: self.strings, addPhoto: { [weak self] in - guard let strongSelf = self else { - return - } - let legacyController = LegacyController(presentation: .custom, theme: defaultPresentationTheme) - legacyController.statusBar.statusBarStyle = .Ignore - - let emptyController = LegacyEmptyController(context: legacyController.context)! - let navigationController = makeLegacyNavigationController(rootController: emptyController) - navigationController.setNavigationBarHidden(true, animated: false) - navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) - - legacyController.bind(controller: navigationController) - - strongSelf.view.endEditing(true) - strongSelf.present(legacyController, in: .window(.root)) - - let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: false, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false)! - let _ = currentAvatarMixin.swap(mixin) - mixin.didFinishWithImage = { image in - guard let strongSelf = self, let image = image else { - return - } - strongSelf.controllerNode.currentPhoto = image - } - /*mixin.didFinishWithDelete = { - }*/ - mixin.didDismiss = { [weak legacyController] in - let _ = currentAvatarMixin.swap(nil) - legacyController?.dismiss() - } - let menuController = mixin.present() - if let menuController = menuController { - menuController.customRemoveFromParentViewController = { [weak legacyController] in - legacyController?.dismiss() - } - } + presentLegacyAvatarPicker(holder: currentAvatarMixin, signup: true, theme: defaultPresentationTheme, present: { c, a in + self?.view.endEditing(true) + self?.present(c, in: .window(.root), with: a) + }, completion: { image in + self?.controllerNode.currentPhoto = image + }) }) self.displayNodeDidLoad() diff --git a/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift b/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift index 6dc3acdf57..f0b2e9669e 100644 --- a/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift +++ b/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift @@ -75,7 +75,7 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel self.currentOptionNode = ASTextNode() self.currentOptionNode.isLayerBacked = true self.currentOptionNode.displaysAsynchronously = false - self.currentOptionNode.attributedText = NSAttributedString(string: self.strings.Login_InfoHelp, font: Font.regular(16.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.currentOptionNode.attributedText = NSAttributedString(string: self.strings.Login_InfoHelp, font: Font.regular(16.0), textColor: theme.textPlaceholderColor, paragraphAlignment: .center) self.firstSeparatorNode = ASDisplayNode() self.firstSeparatorNode.isLayerBacked = true @@ -91,6 +91,11 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel self.firstNameField.textField.textAlignment = .natural self.firstNameField.textField.returnKeyType = .next self.firstNameField.textField.attributedPlaceholder = NSAttributedString(string: self.strings.UserInfo_FirstNamePlaceholder, font: self.firstNameField.textField.font, textColor: self.theme.textPlaceholderColor) + self.firstNameField.textField.autocapitalizationType = .words + self.firstNameField.textField.autocorrectionType = .no + if #available(iOSApplicationExtension 10.0, *) { + self.firstNameField.textField.textContentType = .givenName + } self.lastNameField = TextFieldNode() self.lastNameField.textField.font = Font.regular(20.0) @@ -98,6 +103,11 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel self.lastNameField.textField.textAlignment = .natural self.lastNameField.textField.returnKeyType = .done self.lastNameField.textField.attributedPlaceholder = NSAttributedString(string: strings.UserInfo_LastNamePlaceholder, font: self.lastNameField.textField.font, textColor: self.theme.textPlaceholderColor) + self.lastNameField.textField.autocapitalizationType = .words + self.lastNameField.textField.autocorrectionType = .no + if #available(iOSApplicationExtension 10.0, *) { + self.lastNameField.textField.textContentType = .familyName + } self.currentPhotoNode = ASImageNode() self.currentPhotoNode.isUserInteractionEnabled = false diff --git a/TelegramUI/ChannelMembersSearchContainerNode.swift b/TelegramUI/ChannelMembersSearchContainerNode.swift index f043e536cf..bce0eae313 100644 --- a/TelegramUI/ChannelMembersSearchContainerNode.swift +++ b/TelegramUI/ChannelMembersSearchContainerNode.swift @@ -87,10 +87,10 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable { return lhs.index < rhs.index } - func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void) -> ListViewItem { + func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void) -> ListViewItem { switch self.content { case let .peer(peer): - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in peerSelected(peer, nil) }) case let .participant(participant, label, enabled): @@ -100,7 +100,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable { } else { status = .none } - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: participant.peer), status: status, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: participant.peer), status: status, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: theme, strings: strings, actionTitle: nil, action: nil) }), action: { _ in peerSelected(participant.peer, participant) }) } @@ -113,12 +113,12 @@ struct ChannelMembersSearchContainerTransition { let isSearching: Bool } -private func channelMembersSearchContainerPreparedRecentTransition(from fromEntries: [ChannelMembersSearchEntry], to toEntries: [ChannelMembersSearchEntry], isSearching: Bool, account: Account, theme: PresentationTheme, strings: PresentationStrings, peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void) -> ChannelMembersSearchContainerTransition { +private func channelMembersSearchContainerPreparedRecentTransition(from fromEntries: [ChannelMembersSearchEntry], to toEntries: [ChannelMembersSearchEntry], isSearching: Bool, account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void) -> ChannelMembersSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, peerSelected: peerSelected), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, peerSelected: peerSelected), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, peerSelected: peerSelected), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, peerSelected: peerSelected), directionHint: nil) } return ChannelMembersSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching) } @@ -140,7 +140,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)> + private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)> init(account: Account, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { self.account = account @@ -148,7 +148,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod self.mode = mode self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) + self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.nameSortOrder, self.presentationData.nameDisplayOrder)) self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) @@ -359,7 +359,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod let previousEntries = previousSearchItems.swap(entries) let firstTime = previousEntries == nil - let transition = channelMembersSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, account: account, theme: themeAndStrings.0, strings: themeAndStrings.1, peerSelected: openPeer) + let transition = channelMembersSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, account: account, theme: themeAndStrings.0, strings: themeAndStrings.1, nameSortOrder: themeAndStrings.2, nameDisplayOrder: themeAndStrings.3, peerSelected: openPeer) strongSelf.enqueueTransition(transition, firstTime: firstTime) } })) diff --git a/TelegramUI/ChannelMembersSearchController.swift b/TelegramUI/ChannelMembersSearchController.swift index 12a5e32346..d7e4cde8f7 100644 --- a/TelegramUI/ChannelMembersSearchController.swift +++ b/TelegramUI/ChannelMembersSearchController.swift @@ -58,7 +58,7 @@ final class ChannelMembersSearchController: ViewController { } override func loadDisplayNode() { - self.displayNode = ChannelMembersSearchControllerNode(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, peerId: self.peerId, mode: self.mode, filters: self.filters) + self.displayNode = ChannelMembersSearchControllerNode(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, peerId: self.peerId, mode: self.mode, filters: self.filters) self.controllerNode.navigationBar = self.navigationBar self.controllerNode.requestActivateSearch = { [weak self] in self?.activateSearch() diff --git a/TelegramUI/ChannelMembersSearchControllerNode.swift b/TelegramUI/ChannelMembersSearchControllerNode.swift index 8a5a8c66c6..d43c7d2e67 100644 --- a/TelegramUI/ChannelMembersSearchControllerNode.swift +++ b/TelegramUI/ChannelMembersSearchControllerNode.swift @@ -67,7 +67,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { } } - func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: ChannelMembersSearchInteraction) -> ListViewItem { + func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchInteraction) -> ListViewItem { switch self { case .search: return ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: { @@ -80,7 +80,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { } else { status = .none } - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: nil, action: { _ in + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: nil, action: { _ in interaction.openPeer(participant.peer, participant) }) } @@ -94,12 +94,12 @@ private struct ChannelMembersSearchTransition { let initial: Bool } -private func preparedTransition(from fromEntries: [ChannelMembersSearchEntry]?, to toEntries: [ChannelMembersSearchEntry], account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: ChannelMembersSearchInteraction) -> ChannelMembersSearchTransition { +private func preparedTransition(from fromEntries: [ChannelMembersSearchEntry]?, to toEntries: [ChannelMembersSearchEntry], account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchInteraction) -> ChannelMembersSearchTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries ?? [], rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction), directionHint: nil) } return ChannelMembersSearchTransition(deletions: deletions, insertions: insertions, updates: updates, initial: fromEntries == nil) } @@ -127,7 +127,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { private var disposable: Disposable? private var listControl: PeerChannelMemberCategoryControl? - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) { + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) { self.account = account self.listNode = ListView() self.peerId = peerId @@ -203,7 +203,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { let previous = previousEntries.swap(entries) - strongSelf.enqueueTransition(preparedTransition(from: previous, to: entries, account: account, theme: theme, strings: strings, interaction: interaction)) + strongSelf.enqueueTransition(preparedTransition(from: previous, to: entries, account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction)) }) self.disposable = disposable self.listControl = loadMoreControl diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 9199f8986b..10e6dae9b0 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -253,12 +253,12 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) if self.isNodeLoaded { - self.chatListDisplayNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat) + self.chatListDisplayNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder) } } override public func loadDisplayNode() { - self.displayNode = ChatListControllerNode(account: self.account, groupId: self.groupId, controlsHistoryPreload: self.controlsHistoryPreload, theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat) + self.displayNode = ChatListControllerNode(account: self.account, groupId: self.groupId, controlsHistoryPreload: self.controlsHistoryPreload, theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder) self.chatListDisplayNode.navigationBar = self.navigationBar diff --git a/TelegramUI/ChatListControllerNode.swift b/TelegramUI/ChatListControllerNode.swift index c9a85b538a..a185a9d504 100644 --- a/TelegramUI/ChatListControllerNode.swift +++ b/TelegramUI/ChatListControllerNode.swift @@ -23,10 +23,10 @@ class ChatListControllerNode: ASDisplayNode { var themeAndStrings: (PresentationTheme, PresentationStrings, timeFormat: PresentationTimeFormat) - init(account: Account, groupId: PeerGroupId?, controlsHistoryPreload: Bool, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) { + init(account: Account, groupId: PeerGroupId?, controlsHistoryPreload: Bool, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder) { self.account = account self.groupId = groupId - self.chatListNode = ChatListNode(account: account, groupId: groupId, controlsHistoryPreload: controlsHistoryPreload, mode: .chatList, theme: theme, strings: strings, timeFormat: timeFormat) + self.chatListNode = ChatListNode(account: account, groupId: groupId, controlsHistoryPreload: controlsHistoryPreload, mode: .chatList, theme: theme, strings: strings, timeFormat: timeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder) self.themeAndStrings = (theme, strings, timeFormat) @@ -61,11 +61,11 @@ class ChatListControllerNode: ASDisplayNode { } } - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) { + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder) { self.themeAndStrings = (theme, strings, timeFormat) self.backgroundColor = theme.chatList.backgroundColor - self.chatListNode.updateThemeAndStrings(theme: theme, strings: strings, timeFormat: timeFormat) + self.chatListNode.updateThemeAndStrings(theme: theme, strings: strings, timeFormat: timeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder) self.searchDisplayController?.updateThemeAndStrings(theme: theme, strings: strings) self.chatListEmptyNode?.updateThemeAndStrings(theme: theme, strings: strings) } diff --git a/TelegramUI/ChatListEmptyNode.swift b/TelegramUI/ChatListEmptyNode.swift index 361da7fdd9..68ce3086ca 100644 --- a/TelegramUI/ChatListEmptyNode.swift +++ b/TelegramUI/ChatListEmptyNode.swift @@ -36,7 +36,7 @@ final class ChatListEmptyNode: ASDisplayNode { func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { self.validLayout = size - let textSize = self.textNode.updateLayout(size) + let textSize = self.textNode.updateLayout(CGSize(width: size.width - 40.0, height: size.height)) transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize)) } } diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index 9e2b0616ff..e31929c284 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -168,7 +168,7 @@ private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNode enabled = false } } - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(theme: presentationData.theme, strings: presentationData.strings, account: account, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(theme: presentationData.theme, strings: presentationData.strings, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, account: account, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in if let chatPeer = chatPeer { nodeInteraction.peerSelected(chatPeer) } @@ -229,7 +229,7 @@ private func mappedUpdateEntries(account: Account, nodeInteraction: ChatListNode enabled = false } } - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(theme: presentationData.theme, strings: presentationData.strings, account: account, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(theme: presentationData.theme, strings: presentationData.strings, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, account: account, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in if let chatPeer = chatPeer { nodeInteraction.peerSelected(chatPeer) } @@ -333,12 +333,12 @@ final class ChatListNode: ListView { var isEmptyUpdated: ((Bool) -> Void)? private var wasEmpty: Bool? - init(account: Account, groupId: PeerGroupId?, controlsHistoryPreload: Bool, mode: ChatListNodeMode, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) { + init(account: Account, groupId: PeerGroupId?, controlsHistoryPreload: Bool, mode: ChatListNodeMode, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder) { self.account = account self.controlsHistoryPreload = controlsHistoryPreload self.mode = mode - self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, strings: strings, timeFormat: timeFormat), editing: false, peerIdWithRevealedOptions: nil, peerInputActivities: nil) + self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, strings: strings, timeFormat: timeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder), editing: false, peerIdWithRevealedOptions: nil, peerInputActivities: nil) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.theme = theme @@ -766,7 +766,7 @@ final class ChatListNode: ListView { self.activityStatusesDisposable?.dispose() } - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) { + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder) { if theme !== self.currentState.presentationData.theme || strings !== self.currentState.presentationData.strings || timeFormat != self.currentState.presentationData.timeFormat { self.theme = theme @@ -775,7 +775,7 @@ final class ChatListNode: ListView { } self.updateState { - return $0.withUpdatedPresentationData(ChatListPresentationData(theme: theme, strings: strings, timeFormat: timeFormat)) + return $0.withUpdatedPresentationData(ChatListPresentationData(theme: theme, strings: strings, timeFormat: timeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder)) } } } diff --git a/TelegramUI/ChatListPresentationData.swift b/TelegramUI/ChatListPresentationData.swift index 8bcab322f0..5978e5d2c9 100644 --- a/TelegramUI/ChatListPresentationData.swift +++ b/TelegramUI/ChatListPresentationData.swift @@ -4,10 +4,14 @@ final class ChatListPresentationData { let theme: PresentationTheme let strings: PresentationStrings let timeFormat: PresentationTimeFormat + let nameSortOrder: PresentationPersonNameOrder + let nameDisplayOrder: PresentationPersonNameOrder - init(theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) { + init(theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder) { self.theme = theme self.strings = strings self.timeFormat = timeFormat + self.nameSortOrder = nameSortOrder + self.nameDisplayOrder = nameDisplayOrder } } diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index fd07d75fd8..cad29ddfa3 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -38,13 +38,13 @@ private enum ChatListRecentEntryStableId: Hashable { private enum ChatListRecentEntry: Comparable, Identifiable { case topPeers([Peer], PresentationTheme, PresentationStrings) - case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationTimeFormat, Bool) + case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool) var stableId: ChatListRecentEntryStableId { switch self { case .topPeers: return .topPeers - case let .peer(_, peer, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _): return .peerId(peer.peer.peerId) } } @@ -71,8 +71,8 @@ private enum ChatListRecentEntry: Comparable, Identifiable { } else { return false } - case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsTimeFormat, lhsHasRevealControls): - if case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsTimeFormat, rhsHasRevealControls) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings && lhsTimeFormat == rhsTimeFormat && lhsHasRevealControls == rhsHasRevealControls { + case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder, lhsHasRevealControls): + if case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder, rhsHasRevealControls) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings && lhsTimeFormat == rhsTimeFormat && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsHasRevealControls == rhsHasRevealControls { return true } else { return false @@ -84,11 +84,11 @@ private enum ChatListRecentEntry: Comparable, Identifiable { switch lhs { case .topPeers: return true - case let .peer(lhsIndex, _, _, _, _, _): + case let .peer(lhsIndex, _, _, _, _, _, _, _): switch rhs { case .topPeers: return false - case let .peer(rhsIndex, _, _, _, _, _): + case let .peer(rhsIndex, _, _, _, _, _, _, _): return lhsIndex <= rhsIndex } } @@ -102,7 +102,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { }, peerLongTapped: { peer in peerLongTapped(peer) }) - case let .peer(_, peer, theme, strings, timeFormat, hasRevealControls): + case let .peer(_, peer, theme, strings, timeFormat, nameSortOrder, nameDisplayOrder, hasRevealControls): let primaryPeer: Peer var chatPeer: Peer? let maybeChatPeer = peer.peer.peers[peer.peer.peerId]! @@ -180,7 +180,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { badge = ContactsPeerItemBadge(count: peer.unreadCount, type: isMuted ? .inactive : .active) } - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: status, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: true, editing: false, revealed: hasRevealControls), index: nil, header: ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear.uppercased(), action: { + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: status, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: true, editing: false, revealed: hasRevealControls), index: nil, header: ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear.uppercased(), action: { clearRecentlySearchedPeers() }), action: { _ in if let chatPeer = peer.peer.peers[peer.peer.peerId] { @@ -233,15 +233,15 @@ enum ChatListSearchEntryStableId: Hashable { enum ChatListSearchEntry: Comparable, Identifiable { - case localPeer(Peer, Peer?, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings) - case globalPeer(FoundPeer, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings) + case localPeer(Peer, Peer?, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder) + case globalPeer(FoundPeer, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder) case message(Message, CombinedPeerReadState?, ChatListPresentationData) var stableId: ChatListSearchEntryStableId { switch self { - case let .localPeer(peer, _, _, _, _, _): + case let .localPeer(peer, _, _, _, _, _, _, _): return .localPeerId(peer.id) - case let .globalPeer(peer, _, _, _, _): + case let .globalPeer(peer, _, _, _, _, _, _): return .globalPeerId(peer.peer.id) case let .message(message, _, _): return .messageId(message.id) @@ -250,14 +250,14 @@ enum ChatListSearchEntry: Comparable, Identifiable { static func ==(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool { switch lhs { - case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings): - if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBasge, rhsIndex, rhsTheme, rhsStrings) = rhs, lhsPeer.isEqual(rhsPeer) && arePeersEqual(lhsAssociatedPeer, rhsAssociatedPeer) && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsUnreadBadge == rhsUnreadBasge { + case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder): + if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer.isEqual(rhsPeer) && arePeersEqual(lhsAssociatedPeer, rhsAssociatedPeer) && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge == rhsUnreadBadge { return true } else { return false } - case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings): - if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsUnreadBadge == rhsUnreadBadge { + case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder): + if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge == rhsUnreadBadge { return true } else { return false @@ -285,17 +285,17 @@ enum ChatListSearchEntry: Comparable, Identifiable { static func <(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool { switch lhs { - case let .localPeer(_, _, _, lhsIndex, _, _): - if case let .localPeer(_, _, _, rhsIndex, _, _) = rhs { + case let .localPeer(_, _, _, lhsIndex, _, _, _, _): + if case let .localPeer(_, _, _, rhsIndex, _, _, _, _) = rhs { return lhsIndex <= rhsIndex } else { return true } - case let .globalPeer(_, _, lhsIndex, _, _): + case let .globalPeer(_, _, lhsIndex, _, _, _, _): switch rhs { case .localPeer: return false - case let .globalPeer(_, _, rhsIndex, _, _): + case let .globalPeer(_, _, rhsIndex, _, _, _, _): return lhsIndex <= rhsIndex case .message: return true @@ -311,7 +311,7 @@ enum ChatListSearchEntry: Comparable, Identifiable { func item(account: Account, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction) -> ListViewItem { switch self { - case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings): + case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder): let primaryPeer: Peer var chatPeer: Peer? if let associatedPeer = associatedPeer { @@ -356,10 +356,10 @@ enum ChatListSearchEntry: Comparable, Identifiable { badge = ContactsPeerItemBadge(count: unreadBadge.count, type: unreadBadge.isMuted ? .inactive : .active) } - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .localPeers, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .localPeers, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in interaction.peerSelected(peer) }) - case let .globalPeer(peer, unreadBadge, _, theme, strings): + case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder): var enabled = true if filter.contains(.onlyWriteable) { enabled = canSendMessagesToPeer(peer.peer) @@ -393,7 +393,7 @@ enum ChatListSearchEntry: Comparable, Identifiable { badge = ContactsPeerItemBadge(count: unreadBadge.count, type: unreadBadge.isMuted ? .inactive : .active) } - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .generalSearch, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .generalSearch, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in interaction.peerSelected(peer.peer) }) case let .message(message, readState, presentationData): @@ -506,7 +506,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { self.openMessage = openMessage self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat)) + self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder)) self.recentListNode = ListView() self.listNode = ListView() @@ -526,13 +526,15 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { let foundItems = searchQuery.get() |> mapToSignal { query -> Signal<([ChatListSearchEntry], Bool)?, NoError> in if let query = query, !query.isEmpty { - let accountPeer = account.postbox.loadedPeerWithId(account.peerId) |> take(1) + let accountPeer = account.postbox.loadedPeerWithId(account.peerId) + |> take(1) let foundLocalPeers = account.postbox.searchPeers(query: query.lowercased(), groupId: groupId) |> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in return combineLatest(local.map {account.postbox.peerView(id: $0.peerId)}) |> map { views in return (views, local) } - } |> mapToSignal{ viewsAndPeers -> Signal<(peers: [RenderedPeer], unread: [PeerId : UnreadSearchBadge]), NoError> in + } + |> mapToSignal{ viewsAndPeers -> Signal<(peers: [RenderedPeer], unread: [PeerId : UnreadSearchBadge]), NoError> in return account.postbox.unreadMessageCountsView(items: viewsAndPeers.0.map {.peer($0.peerId)}) |> map { values in var unread:[PeerId: UnreadSearchBadge] = [:] for peerView in viewsAndPeers.0 { @@ -554,8 +556,6 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { } } - - let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError> if groupId == nil { foundRemotePeers = (.single(([], [], true)) |> then(searchPeers(account: account, query: query) |> map { ($0.0, $0.1, false) } @@ -570,8 +570,8 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { location = .general } let foundRemoteMessages: Signal<(([Message], [PeerId : CombinedPeerReadState]), Bool), NoError> = .single((([], [:]), true)) |> then(searchMessages(account: account, location: location, query: query) - |> map { ($0, false) } - |> delay(0.2, queue: Queue.concurrentDefaultQueue())) + |> map { ($0, false) } + |> delay(0.2, queue: Queue.concurrentDefaultQueue())) return combineLatest(accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationDataPromise.get()) |> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationData -> ([ChatListSearchEntry], Bool)? in @@ -584,7 +584,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { if presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(query.lowercased()) { if !existingPeerIds.contains(accountPeer.id) { existingPeerIds.insert(accountPeer.id) - entries.append(.localPeer(accountPeer, nil, nil, index, presentationData.theme, presentationData.strings)) + entries.append(.localPeer(accountPeer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder)) index += 1 } } @@ -597,7 +597,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { if let associatedPeerId = peer.associatedPeerId { associatedPeer = renderedPeer.peers[associatedPeerId] } - entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings)) + entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder)) index += 1 } } @@ -606,7 +606,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { for peer in foundRemotePeers.0 { if !existingPeerIds.contains(peer.peer.id) { existingPeerIds.insert(peer.peer.id) - entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings)) + entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder)) index += 1 } } @@ -615,7 +615,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { for peer in foundRemotePeers.1 { if !existingPeerIds.contains(peer.peer.id) { existingPeerIds.insert(peer.peer.id) - entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings)) + entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder)) index += 1 } } @@ -696,7 +696,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { } peerIds.insert(peer.id) - entries.append(.peer(index: index, peer: searchedPeer, presentationData.theme, presentationData.strings, presentationData.timeFormat, state.peerIdWithRevealedOptions == peer.id)) + entries.append(.peer(index: index, peer: searchedPeer, presentationData.theme, presentationData.strings, presentationData.timeFormat, presentationData.nameSortOrder, presentationData.nameDisplayOrder, state.peerIdWithRevealedOptions == peer.id)) index += 1 } } diff --git a/TelegramUI/ComposeControllerNode.swift b/TelegramUI/ComposeControllerNode.swift index b92327de88..53f357a189 100644 --- a/TelegramUI/ComposeControllerNode.swift +++ b/TelegramUI/ComposeControllerNode.swift @@ -34,7 +34,7 @@ final class ComposeControllerNode: ASDisplayNode { var openCreateNewSecretChatImpl: (() -> Void)? var openCreateNewChannelImpl: (() -> Void)? - self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, ordering: .lastFirst, options: [ + self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, options: [ ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewGroup, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateGroupActionIcon"), color: presentationData.theme.list.itemAccentColor), action: { openCreateNewGroupImpl?() }), diff --git a/TelegramUI/ContactListNode.swift b/TelegramUI/ContactListNode.swift index 2a9b2766f8..7699177e84 100644 --- a/TelegramUI/ContactListNode.swift +++ b/TelegramUI/ContactListNode.swift @@ -118,7 +118,7 @@ enum ContactListPeer: Equatable { private enum ContactListNodeEntry: Comparable, Identifiable { case search(PresentationTheme, PresentationStrings) case option(Int, ContactListAdditionalOption, PresentationTheme, PresentationStrings) - case peer(Int, ContactListPeer, PeerPresence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationTimeFormat, Bool) + case peer(Int, ContactListPeer, PeerPresence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool) var stableId: ContactListNodeEntryId { switch self { @@ -126,7 +126,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { return .search case let .option(index, _, _, _): return .option(index: index) - case let .peer(_, peer, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, _): switch peer { case let .peer(peer, _): return .peerId(peer.id.toInt64()) @@ -139,12 +139,12 @@ private enum ContactListNodeEntry: Comparable, Identifiable { func item(account: Account, interaction: ContactListNodeInteraction) -> ListViewItem { switch self { case let .search(theme, strings): - return ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: { + return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: { interaction.activateSearch() }) case let .option(_, option, theme, _): return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, action: option.action) - case let .peer(_, peer, presence, header, selection, theme, strings, timeFormat, enabled): + case let .peer(_, peer, presence, header, selection, theme, strings, timeFormat, nameSortOrder, nameDisplayOrder, enabled): let status: ContactsPeerItemStatus let itemPeer: ContactsPeerItemPeer switch peer { @@ -161,7 +161,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { status = .none itemPeer = .deviceContact(stableId: id, contact: contact) } - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in interaction.openPeer(peer) }) } @@ -181,9 +181,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable { } else { return false } - case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat, lhsEnabled): + case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder, lhsEnabled): switch rhs { - case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat, rhsEnabled): + case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder, rhsEnabled): if lhsIndex != rhsIndex { return false } @@ -212,6 +212,12 @@ private enum ContactListNodeEntry: Comparable, Identifiable { if lhsTimeFormat != rhsTimeFormat { return false } + if lhsSortOrder != rhsSortOrder { + return false + } + if lhsDisplayOrder != rhsDisplayOrder { + return false + } if lhsEnabled != rhsEnabled { return false } @@ -235,11 +241,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable { case .peer: return true } - case let .peer(lhsIndex, _, _, _, _, _, _, _, _): + case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _): switch rhs { case .search, .option: return false - case let .peer(rhsIndex, _, _, _, _, _, _, _, _): + case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _): return lhsIndex < rhsIndex } } @@ -247,7 +253,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { } private extension PeerIndexNameRepresentation { - func isLessThan(other: PeerIndexNameRepresentation, ordering: ContactNameOrdering) -> ComparisonResult { + func isLessThan(other: PeerIndexNameRepresentation, ordering: PresentationPersonNameOrder) -> ComparisonResult { switch self { case let .title(lhsTitle, _): switch other { @@ -292,7 +298,7 @@ private extension PeerIndexNameRepresentation { } } -private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat, disabledPeerIds:Set) -> [ContactListNodeEntry] { +private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds:Set) -> [ContactListNodeEntry] { var entries: [ContactListNodeEntry] = [] var orderedPeers: [ContactListPeer] @@ -326,9 +332,9 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] for i in 0 ..< options.count { entries.append(.option(i, options[i], theme, strings)) } - case let .natural(displaySearch, ordering, options): + case let .natural(displaySearch, options): orderedPeers = peers.sorted(by: { lhs, rhs in - let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: ordering) + let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: sortOrder) if result == .orderedSame { if case let .peer(lhsPeer, _) = lhs, case let .peer(rhsPeer, _) = rhs { return lhsPeer.id < rhsPeer.id @@ -352,10 +358,19 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] indexHeader = c } case let .personName(first, last, _, _): - if let c = last.utf16.first { - indexHeader = c - } else if let c = first.utf16.first { - indexHeader = c + switch sortOrder { + case .firstLast: + if let c = first.utf16.first { + indexHeader = c + } else if let c = last.utf16.first { + indexHeader = c + } + case .lastFirst: + if let c = last.utf16.first { + indexHeader = c + } else if let c = first.utf16.first { + indexHeader = c + } } } let header: ContactListNameIndexHeader @@ -424,12 +439,12 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] } let enabled: Bool switch orderedPeers[i] { - case let .peer(peer, _): - enabled = !disabledPeerIds.contains(peer.id) - default: - enabled = true + case let .peer(peer, _): + enabled = !disabledPeerIds.contains(peer.id) + default: + enabled = true } - entries.append(.peer(i, orderedPeers[i], presence, header, selection, theme, strings, timeFormat, enabled)) + entries.append(.peer(i, orderedPeers[i], presence, header, selection, theme, strings, timeFormat, sortOrder, displayOrder, enabled)) } return entries } @@ -462,14 +477,9 @@ public struct ContactListAdditionalOption: Equatable { } } -enum ContactNameOrdering { - case firstLast - case lastFirst -} - enum ContactListPresentation { case orderedByPresence(options: [ContactListAdditionalOption]) - case natural(displaySearch: Bool, ordering: ContactNameOrdering, options: [ContactListAdditionalOption]) + case natural(displaySearch: Bool, options: [ContactListAdditionalOption]) case search(signal: Signal, searchDeviceContacts: Bool) } @@ -556,7 +566,7 @@ final class ContactListNode: ASDisplayNode { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationTimeFormat)> + private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder)> init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) { self.account = account @@ -567,7 +577,7 @@ final class ContactListNode: ASDisplayNode { self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.timeFormat)) + self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.timeFormat, self.presentationData.nameSortOrder, self.presentationData.nameDisplayOrder)) super.init() @@ -670,7 +680,7 @@ final class ContactListNode: ASDisplayNode { peers.append(.deviceContact(stableId, contact)) } - let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: [:], presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2, disabledPeerIds: disabledPeerIds) + let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: [:], presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2, sortOrder: themeAndStrings.3, displayOrder: themeAndStrings.4, disabledPeerIds: disabledPeerIds) let previous = previousEntries.swap(entries) return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, animated: false)) } @@ -710,7 +720,7 @@ final class ContactListNode: ASDisplayNode { } } - let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: peers, presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2, disabledPeerIds: disabledPeerIds) + let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: peers, presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2, sortOrder: themeAndStrings.3, displayOrder: themeAndStrings.4, disabledPeerIds: disabledPeerIds) let previous = previousEntries.swap(entries) let animated: Bool if let previous = previous { @@ -743,7 +753,7 @@ final class ContactListNode: ASDisplayNode { if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { strongSelf.backgroundColor = presentationData.theme.chatList.backgroundColor - strongSelf.themeAndStringsPromise.set(.single((presentationData.theme, presentationData.strings, presentationData.timeFormat))) + strongSelf.themeAndStringsPromise.set(.single((presentationData.theme, presentationData.strings, presentationData.timeFormat, presentationData.nameSortOrder, presentationData.nameDisplayOrder))) strongSelf.listNode.forEachAccessoryItemNode({ accessoryItemNode in if let accessoryItemNode = accessoryItemNode as? ContactsSectionHeaderAccessoryItemNode { diff --git a/TelegramUI/ContactMultiselectionControllerNode.swift b/TelegramUI/ContactMultiselectionControllerNode.swift index 91ece01705..e507a5fb2f 100644 --- a/TelegramUI/ContactMultiselectionControllerNode.swift +++ b/TelegramUI/ContactMultiselectionControllerNode.swift @@ -48,7 +48,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { self.account = account self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: false, ordering: .lastFirst, options: options), filters: filters, selectionState: ContactListNodeGroupSelectionState()) + self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: false, options: options), filters: filters, selectionState: ContactListNodeGroupSelectionState()) self.tokenListNode = EditableTokenListNode(theme: EditableTokenListNodeTheme(backgroundColor: self.presentationData.theme.rootController.navigationBar.backgroundColor, separatorColor: self.presentationData.theme.rootController.navigationBar.separatorColor, placeholderTextColor: self.presentationData.theme.list.itemPlaceholderTextColor, primaryTextColor: self.presentationData.theme.list.itemPrimaryTextColor, selectedTextColor: self.presentationData.theme.list.itemAccentColor, keyboardColor: self.presentationData.theme.chatList.searchBarKeyboardColor), placeholder: self.presentationData.strings.Compose_TokenListPlaceholder) super.init() diff --git a/TelegramUI/ContactSelectionControllerNode.swift b/TelegramUI/ContactSelectionControllerNode.swift index a16b8ccd64..c34bfe304a 100644 --- a/TelegramUI/ContactSelectionControllerNode.swift +++ b/TelegramUI/ContactSelectionControllerNode.swift @@ -36,14 +36,13 @@ final class ContactSelectionControllerNode: ASDisplayNode { init(account: Account, options: [ContactListAdditionalOption], displayDeviceContacts: Bool) { self.account = account + self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } self.displayDeviceContacts = displayDeviceContacts - self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, ordering: .lastFirst, options: options)) + self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, options: options)) self.dimNode = ASDisplayNode() - self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - super.init() self.setViewBlock({ diff --git a/TelegramUI/ContactsPeerItem.swift b/TelegramUI/ContactsPeerItem.swift index 0d45036b22..81f4c0049b 100644 --- a/TelegramUI/ContactsPeerItem.swift +++ b/TelegramUI/ContactsPeerItem.swift @@ -108,6 +108,8 @@ enum ContactsPeerItemPeer: Equatable { class ContactsPeerItem: ListViewItem { let theme: PresentationTheme let strings: PresentationStrings + let sortOrder: PresentationPersonNameOrder + let displayOrder: PresentationPersonNameOrder let account: Account let peerMode: ContactsPeerItemPeerMode let peer: ContactsPeerItemPeer @@ -126,9 +128,11 @@ class ContactsPeerItem: ListViewItem { let header: ListViewItemHeader? - init(theme: PresentationTheme, strings: PresentationStrings, account: Account, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil) { + init(theme: PresentationTheme, strings: PresentationStrings, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, account: Account, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil) { self.theme = theme self.strings = strings + self.sortOrder = sortOrder + self.displayOrder = displayOrder self.account = account self.peerMode = peerMode self.peer = peer @@ -444,9 +448,9 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { titleAttributedString = NSAttributedString(string: item.strings.DialogList_SavedMessages, font: titleBoldFont, textColor: textColor) } else if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() - string.append(NSAttributedString(string: firstName, font: titleFont, textColor: textColor)) + string.append(NSAttributedString(string: firstName, font: item.sortOrder == .firstLast ? titleBoldFont : titleFont, textColor: textColor)) string.append(NSAttributedString(string: " ", font: titleFont, textColor: textColor)) - string.append(NSAttributedString(string: lastName, font: titleBoldFont, textColor: textColor)) + string.append(NSAttributedString(string: lastName, font: item.sortOrder == .firstLast ? titleFont : titleBoldFont, textColor: textColor)) titleAttributedString = string } else if let firstName = user.firstName, !firstName.isEmpty { titleAttributedString = NSAttributedString(string: firstName, font: titleBoldFont, textColor: textColor) diff --git a/TelegramUI/ContactsSearchContainerNode.swift b/TelegramUI/ContactsSearchContainerNode.swift index a031fa7613..1323757d22 100644 --- a/TelegramUI/ContactsSearchContainerNode.swift +++ b/TelegramUI/ContactsSearchContainerNode.swift @@ -41,7 +41,7 @@ private struct ContactListSearchEntry: Identifiable, Comparable { return lhs.index < rhs.index } - func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, openPeer: @escaping (ContactListPeer) -> Void) -> ListViewItem { + func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, openPeer: @escaping (ContactListPeer) -> Void) -> ListViewItem { let header: ListViewItemHeader let status: ContactsPeerItemStatus switch self.group { @@ -50,7 +50,7 @@ private struct ContactListSearchEntry: Identifiable, Comparable { status = .none case .global: header = ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: nil, action: nil) - if case let .peer(peer, _) = self.peer, let addressName = peer.addressName { + if case let .peer(peer, _) = self.peer, let _ = peer.addressName { status = .addressName("") } else { status = .none @@ -67,7 +67,7 @@ private struct ContactListSearchEntry: Identifiable, Comparable { case let .deviceContact(stableId, contact): peerItem = .deviceContact(stableId: stableId, contact: contact) } - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: peerItem, status: status, enabled: self.enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: peerItem, status: status, enabled: self.enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in openPeer(peer) }) } @@ -80,12 +80,12 @@ struct ContactListSearchContainerTransition { let isSearching: Bool } -private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, account: Account, theme: PresentationTheme, strings: PresentationStrings, openPeer: @escaping (ContactListPeer) -> Void) -> ContactListSearchContainerTransition { +private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, account: Account, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, openPeer: @escaping (ContactListPeer) -> Void) -> ContactListSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, openPeer: openPeer), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, openPeer: openPeer), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, openPeer: openPeer), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, openPeer: openPeer), directionHint: nil) } return ContactListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching) } @@ -176,12 +176,12 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode { var disabledPeerIds = Set() for filter in filters { switch filter { - case .excludeSelf: - existingPeerIds.insert(account.peerId) - case let .exclude(peerIds): - existingPeerIds = existingPeerIds.union(peerIds) - case let .disable(peerIds): - disabledPeerIds = disabledPeerIds.union(peerIds) + case .excludeSelf: + existingPeerIds.insert(account.peerId) + case let .exclude(peerIds): + existingPeerIds = existingPeerIds.union(peerIds) + case let .disable(peerIds): + disabledPeerIds = disabledPeerIds.union(peerIds) } } var existingNormalizedPhoneNumbers = Set() @@ -222,6 +222,9 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode { } } for peer in remotePeers.1 { + if !(peer.peer is TelegramUser) { + continue + } if !existingPeerIds.contains(peer.peer.id) { existingPeerIds.insert(peer.peer.id) @@ -264,25 +267,10 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode { if let strongSelf = self { let previousItems = previousSearchItems.swap(items ?? []) - let transition = contactListSearchContainerPreparedRecentTransition(from: previousItems, to: items ?? [], isSearching: items != nil, account: account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, openPeer: { peer in self?.listNode.clearHighlightAnimated(true) + let transition = contactListSearchContainerPreparedRecentTransition(from: previousItems, to: items ?? [], isSearching: items != nil, account: account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, openPeer: { peer in self?.listNode.clearHighlightAnimated(true) self?.openPeer(peer) }) - /*var listItems: [ListViewItem] = [] - for item in items { - switch item { - case let .peer(peer, theme, strings): - - - listItems.append(ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: peer, chatPeer: peer, status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { [weak self] _ in - if let openPeer = self?.openPeer { - self?.listNode.clearHighlightAnimated(true) - openPeer(peer.id) - } - })) - } - }*/ - strongSelf.enqueueTransition(transition) } })) diff --git a/TelegramUI/CreatePasswordController.swift b/TelegramUI/CreatePasswordController.swift index 8b5d5e25b1..8eb4e39c80 100644 --- a/TelegramUI/CreatePasswordController.swift +++ b/TelegramUI/CreatePasswordController.swift @@ -402,7 +402,10 @@ func createPasswordController(account: Account, state: CreatePasswordState, comp resultItemNode.focus() } } - controller.didAppear = { + controller.didAppear = { firstTime in + if !firstTime { + return + } initialFocusImpl?() } diff --git a/TelegramUI/GroupInfoController.swift b/TelegramUI/GroupInfoController.swift index 1c77ff3528..47d66e5a54 100644 --- a/TelegramUI/GroupInfoController.swift +++ b/TelegramUI/GroupInfoController.swift @@ -719,6 +719,8 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa } if channel.hasAdminRights(.canInviteUsers) { canAddMembers = true + } else if case let .group(info) = channel.info, info.flags.contains(.everyMemberCanInviteMembers) { + canAddMembers = true } } diff --git a/TelegramUI/HashtagSearchController.swift b/TelegramUI/HashtagSearchController.swift index c9ca216d94..989e8d0a29 100644 --- a/TelegramUI/HashtagSearchController.swift +++ b/TelegramUI/HashtagSearchController.swift @@ -33,7 +33,7 @@ final class HashtagSearchController: TelegramController { self.title = query self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat) + let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder) let location: SearchMessagesLocation = .general let search = searchMessages(account: account, location: location, query: query) diff --git a/TelegramUI/InviteContactsController.swift b/TelegramUI/InviteContactsController.swift index 7404dc30ad..9df17cec36 100644 --- a/TelegramUI/InviteContactsController.swift +++ b/TelegramUI/InviteContactsController.swift @@ -39,7 +39,7 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr self.scrollToTop = { [weak self] in if let strongSelf = self { - //strongSelf.contactsNode.listNode.scrollToTop() + strongSelf.contactsNode.scrollToTop() } } diff --git a/TelegramUI/InviteContactsControllerNode.swift b/TelegramUI/InviteContactsControllerNode.swift index 6ee0f2b719..225d51e43b 100644 --- a/TelegramUI/InviteContactsControllerNode.swift +++ b/TelegramUI/InviteContactsControllerNode.swift @@ -66,7 +66,7 @@ private final class InviteContactsInteraction { private enum InviteContactsEntry: Comparable, Identifiable { case search(PresentationTheme, PresentationStrings) case option(Int, ContactListAdditionalOption, PresentationTheme, PresentationStrings) - case peer(Int, DeviceContactStableId, DeviceContactBasicData, Int32, ContactsPeerItemSelection, PresentationTheme, PresentationStrings) + case peer(Int, DeviceContactStableId, DeviceContactBasicData, Int32, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder) var stableId: InviteContactsEntryId { switch self { @@ -74,7 +74,7 @@ private enum InviteContactsEntry: Comparable, Identifiable { return .search case let .option(index, _, _, _): return .option(index: index) - case let .peer(_, id, _, _, _, _, _): + case let .peer(_, id, _, _, _, _, _, _, _): return .contactId(id) } } @@ -87,7 +87,7 @@ private enum InviteContactsEntry: Comparable, Identifiable { }) case let .option(_, option, theme, _): return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, action: option.action) - case let .peer(_, id, contact, count, selection, theme, strings): + case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder): let status: ContactsPeerItemStatus if count != 0 { status = .custom(strings.Contacts_ImportersCount(count)) @@ -95,7 +95,7 @@ private enum InviteContactsEntry: Comparable, Identifiable { status = .none } let peer = TelegramUser(id: PeerId(namespace: -1, id: 0), accessHash: nil, firstName: contact.firstName, lastName: contact.lastName, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: status, enabled: true, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in + return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: account, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: status, enabled: true, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in interaction.toggleContact(id) }) } @@ -115,9 +115,9 @@ private enum InviteContactsEntry: Comparable, Identifiable { } else { return false } - case let .peer(lhsIndex, lhsId, lhsContact, lhsCount, lhsSelection, lhsTheme, lhsStrings): + case let .peer(lhsIndex, lhsId, lhsContact, lhsCount, lhsSelection, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder): switch rhs { - case let .peer(rhsIndex, rhsId, rhsContact, rhsCount, rhsSelection, rhsTheme, rhsStrings): + case let .peer(rhsIndex, rhsId, rhsContact, rhsCount, rhsSelection, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder): if lhsIndex != rhsIndex { return false } @@ -139,6 +139,12 @@ private enum InviteContactsEntry: Comparable, Identifiable { if lhsStrings !== rhsStrings { return false } + if lhsSortOrder != rhsSortOrder { + return false + } + if lhsDisplayOrder != rhsDisplayOrder { + return false + } return true default: return false @@ -159,11 +165,11 @@ private enum InviteContactsEntry: Comparable, Identifiable { case .peer: return true } - case let .peer(lhsIndex, _, _, _, _, _, _): + case let .peer(lhsIndex, _, _, _, _, _, _, _, _): switch rhs { case .search, .option: return false - case let .peer(rhsIndex, _, _, _, _, _, _): + case let .peer(rhsIndex, _, _, _, _, _, _, _, _): return lhsIndex < rhsIndex } } @@ -214,7 +220,7 @@ struct InviteContactsGroupSelectionState: Equatable { } } -private func inviteContactsEntries(accountPeer: Peer?, sortedContacts: [(DeviceContactStableId, DeviceContactBasicData, Int32)], selectionState: InviteContactsGroupSelectionState, theme: PresentationTheme, strings: PresentationStrings, interaction: InviteContactsInteraction) -> [InviteContactsEntry] { +private func inviteContactsEntries(accountPeer: Peer?, sortedContacts: [(DeviceContactStableId, DeviceContactBasicData, Int32)], selectionState: InviteContactsGroupSelectionState, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: InviteContactsInteraction) -> [InviteContactsEntry] { var entries: [InviteContactsEntry] = [] entries.append(.search(theme, strings)) @@ -225,7 +231,7 @@ private func inviteContactsEntries(accountPeer: Peer?, sortedContacts: [(DeviceC var index = 0 for (id, contact, count) in sortedContacts { - entries.append(.peer(index, id, contact, count, .selectable(selected: selectionState.selectedContactIndices[id] != nil), theme, strings)) + entries.append(.peer(index, id, contact, count, .selectable(selected: selectionState.selectedContactIndices[id] != nil), theme, strings, nameSortOrder, nameDisplayOrder)) index += 1 } @@ -290,7 +296,7 @@ final class InviteContactsControllerNode: ASDisplayNode { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)> + private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)> private let _ready = Promise() private var readyValue = false { @@ -313,7 +319,7 @@ final class InviteContactsControllerNode: ASDisplayNode { self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) + self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.nameSortOrder, self.presentationData.nameDisplayOrder)) self.listNode = ListView() @@ -427,7 +433,7 @@ final class InviteContactsControllerNode: ASDisplayNode { transition = (combineLatest(sortedContacts, selectionStateSignal, themeAndStringsPromise.get()) |> mapToQueue { sortedContacts, selectionState, themeAndStrings -> Signal in let signal = deferred { () -> Signal in - let entries = inviteContactsEntries(accountPeer: nil, sortedContacts: sortedContacts, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, interaction: interaction) + let entries = inviteContactsEntries(accountPeer: nil, sortedContacts: sortedContacts, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, nameSortOrder: themeAndStrings.2, nameDisplayOrder: themeAndStrings.3, interaction: interaction) let previous = previousEntries.swap(entries) let animated: Bool if let previous = previous { @@ -485,6 +491,10 @@ final class InviteContactsControllerNode: ASDisplayNode { self.searchDisplayController?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) } + func scrollToTop() { + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { let hadValidLayout = self.validLayout != nil self.validLayout = (layout, navigationBarHeight) diff --git a/TelegramUI/ItemListController.swift b/TelegramUI/ItemListController.swift index fe8020c3c2..28e160d987 100644 --- a/TelegramUI/ItemListController.swift +++ b/TelegramUI/ItemListController.swift @@ -141,7 +141,7 @@ class ItemListController: ViewController { private var didPlayPresentationAnimation = false private(set) var didAppearOnce = false - var didAppear: (() -> Void)? + var didAppear: ((Bool) -> Void)? var titleControlValueChanged: ((Int) -> Void)? @@ -419,10 +419,8 @@ class ItemListController: ViewController { } } - if !self.didAppearOnce { - self.didAppearOnce = true - self.didAppear?() - } + self.didAppear?(!self.didAppearOnce) + self.didAppearOnce = true } override func viewWillDisappear(_ animated: Bool) { diff --git a/TelegramUI/LegacyAvatarPicker.swift b/TelegramUI/LegacyAvatarPicker.swift new file mode 100644 index 0000000000..e225c42816 --- /dev/null +++ b/TelegramUI/LegacyAvatarPicker.swift @@ -0,0 +1,37 @@ +import Foundation +import Display +import SwiftSignalKit +import LegacyComponents + +func presentLegacyAvatarPicker(holder: Atomic, signup: Bool, theme: PresentationTheme, present: (ViewController, Any?) -> Void, completion: @escaping (UIImage) -> Void) { + let legacyController = LegacyController(presentation: .custom, theme: theme) + legacyController.statusBar.statusBarStyle = .Ignore + + let emptyController = LegacyEmptyController(context: legacyController.context)! + let navigationController = makeLegacyNavigationController(rootController: emptyController) + navigationController.setNavigationBarHidden(true, animated: false) + navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) + + legacyController.bind(controller: navigationController) + + present(legacyController, nil) + + let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: false, hasViewButton: false, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: signup)! + let _ = holder.swap(mixin) + mixin.didFinishWithImage = { image in + guard let image = image else { + return + } + completion(image) + } + mixin.didDismiss = { [weak legacyController] in + let _ = holder.swap(nil) + legacyController?.dismiss() + } + let menuController = mixin.present() + if let menuController = menuController { + menuController.customRemoveFromParentViewController = { [weak legacyController] in + legacyController?.dismiss() + } + } +} diff --git a/TelegramUI/OngoingCallThreadLocalContext.mm b/TelegramUI/OngoingCallThreadLocalContext.mm index 40894cafeb..c2a5054369 100644 --- a/TelegramUI/OngoingCallThreadLocalContext.mm +++ b/TelegramUI/OngoingCallThreadLocalContext.mm @@ -257,14 +257,15 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { config.logFilePath = ""; config.statsDumpFilePath = ""; - _controller->SetConfig(config); - - _controller->SetEncryptionKey((char *)key.bytes, isOutgoing); - /*releasable*/ - _controller->SetRemoteEndpoints(endpoints, _allowP2P, maxLayer); - _controller->Start(); - - _controller->Connect(); + if (_controller != nil) { + _controller->SetConfig(config); + + _controller->SetEncryptionKey((char *)key.bytes, isOutgoing); + _controller->SetRemoteEndpoints(endpoints, _allowP2P, maxLayer); + _controller->Start(); + + _controller->Connect(); + } } - (void)stop { diff --git a/TelegramUI/PeerSelectionControllerNode.swift b/TelegramUI/PeerSelectionControllerNode.swift index 450a4abf6d..660a6e7f40 100644 --- a/TelegramUI/PeerSelectionControllerNode.swift +++ b/TelegramUI/PeerSelectionControllerNode.swift @@ -62,7 +62,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.segmentedControl.tintColor = self.presentationData.theme.rootController.navigationBar.accentTextColor self.segmentedControl.selectedSegmentIndex = 0 - self.chatListNode = ChatListNode(account: account, groupId: nil, controlsHistoryPreload: false, mode: .peers(filter: filter), theme: presentationData.theme, strings: presentationData.strings, timeFormat: presentationData.timeFormat) + self.chatListNode = ChatListNode(account: account, groupId: nil, controlsHistoryPreload: false, mode: .peers(filter: filter), theme: presentationData.theme, strings: presentationData.strings, timeFormat: presentationData.timeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder) super.init() @@ -107,7 +107,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { private func updateThemeAndStrings() { self.searchDisplayController?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) - self.chatListNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat) + self.chatListNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder) } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { @@ -315,7 +315,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.recursivelyEnsureDisplaySynchronously(true) contactListNode.enableUpdates = true } else { - let contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, ordering: .lastFirst, options: [])) + let contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, options: [])) self.contactListNode = contactListNode contactListNode.enableUpdates = true contactListNode.activateSearch = { [weak self] in diff --git a/TelegramUI/PresentationData.swift b/TelegramUI/PresentationData.swift index d88f57f561..fc3dc71bfb 100644 --- a/TelegramUI/PresentationData.swift +++ b/TelegramUI/PresentationData.swift @@ -2,25 +2,38 @@ import Foundation import SwiftSignalKit import Postbox import TelegramCore +import Contacts +import AddressBook public enum PresentationTimeFormat { case regular case military } +public enum PresentationPersonNameOrder { + case firstLast + case lastFirst +} + + + public final class PresentationData: Equatable { public let strings: PresentationStrings public let theme: PresentationTheme public let chatWallpaper: TelegramWallpaper public let fontSize: PresentationFontSize public let timeFormat: PresentationTimeFormat + public let nameDisplayOrder: PresentationPersonNameOrder + public let nameSortOrder: PresentationPersonNameOrder - public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, fontSize: PresentationFontSize, timeFormat: PresentationTimeFormat) { + public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, fontSize: PresentationFontSize, timeFormat: PresentationTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder) { self.strings = strings self.theme = theme self.chatWallpaper = chatWallpaper self.fontSize = fontSize self.timeFormat = timeFormat + self.nameDisplayOrder = nameDisplayOrder + self.nameSortOrder = nameSortOrder } public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool { @@ -71,6 +84,40 @@ private func currentTimeFormat() -> PresentationTimeFormat { } } +private func currentPersonNameSortOrder() -> PresentationPersonNameOrder { + if #available(iOSApplicationExtension 9.0, *) { + switch CNContactsUserDefaults.shared().sortOrder { + case .givenName: + return .firstLast + default: + return .lastFirst + } + } else { + if ABPersonGetSortOrdering() == kABPersonSortByFirstName { + return .firstLast + } else { + return .lastFirst + } + } +} + +private func currentPersonNameDisplayOrder() -> PresentationPersonNameOrder { + if #available(iOSApplicationExtension 9.0, *) { + switch CNContactFormatter.nameOrder(for: CNContact()) { + case .givenNameFirst: + return .firstLast + default: + return .lastFirst + } + } else { + if ABPersonGetCompositeNameFormat() == kABPersonCompositeNameFormatFirstNameFirst { + return .firstLast + } else { + return .lastFirst + } + } +} + public final class InitialPresentationDataAndSettings { public let presentationData: PresentationData public let automaticMediaDownloadSettings: AutomaticMediaDownloadSettings @@ -186,7 +233,9 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal Signal [SettingsEntry] { +private func settingsEntries(presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, hasPassport: Bool) -> [SettingsEntry] { var entries: [SettingsEntry] = [] if let peer = peerViewMainPeer(view) as? TelegramUser { @@ -383,8 +383,8 @@ private func settingsEntries(presentationData: PresentationData, state: Settings entries.append(.themes(presentationData.theme, SettingsItemIcons.appearance, presentationData.strings.ChatSettings_Appearance.lowercased().capitalized)) entries.append(.language(presentationData.theme, SettingsItemIcons.language, presentationData.strings.Settings_AppLanguage, presentationData.strings.Localization_LanguageName)) - if GlobalExperimentalSettings.enablePassport { - entries.append(.passport(presentationData.theme, SettingsItemIcons.secureId, "Telegram Passport", "")) + if hasPassport { + entries.append(.passport(presentationData.theme, SettingsItemIcons.secureId, presentationData.strings.Settings_Passport, "")) } entries.append(.askAQuestion(presentationData.theme, SettingsItemIcons.support, presentationData.strings.Settings_Support)) @@ -422,6 +422,9 @@ public func settingsController(account: Account, accountManager: AccountManager) let hiddenAvatarRepresentationDisposable = MetaDisposable() actionsDisposable.add(hiddenAvatarRepresentationDisposable) + let updatePassportDisposable = MetaDisposable() + actionsDisposable.add(updatePassportDisposable) + let currentAvatarMixin = Atomic(value: nil) var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)? @@ -624,8 +627,17 @@ public func settingsController(account: Account, accountManager: AccountManager) archivedPacks.set(.single(nil) |> then(archivedStickerPacks(account: account) |> map(Optional.init))) - let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), peerView, account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get())) - |> map { presentationData, state, view, preferences, featuredAndArchived -> (ItemListControllerState, (ItemListNodeState, SettingsEntry.ItemGenerationArguments)) in + let hasPassport = ValuePromise(false) + let updatePassport: () -> Void = { + updatePassportDisposable.set((twoStepAuthData(account.network) + |> deliverOnMainQueue).start(next: { value in + hasPassport.set(value.hasSecretValues) + })) + } + updatePassport() + + let signal = combineLatest(account.telegramApplicationContext.presentationData, statePromise.get(), peerView, account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get()), hasPassport.get()) + |> map { presentationData, state, view, preferences, featuredAndArchived, hasPassport -> (ItemListControllerState, (ItemListNodeState, SettingsEntry.ItemGenerationArguments)) in let proxySettings: ProxySettings if let value = preferences.values[PreferencesKeys.proxySettings] as? ProxySettings { proxySettings = value @@ -649,7 +661,7 @@ public func settingsController(account: Account, accountManager: AccountManager) } } - let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1), style: .blocks) + let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, hasPassport: hasPassport), style: .blocks) return (controllerState, (listState, arguments)) } |> afterDisposed { @@ -700,6 +712,9 @@ public func settingsController(account: Account, accountManager: AccountManager) controller.tabBarItemDebugTapAction = { pushControllerImpl?(debugController(account: account, accountManager: accountManager)) } + controller.didAppear = { _ in + updatePassport() + } return controller } diff --git a/TelegramUI/TelegramInitializeLegacyComponents.swift b/TelegramUI/TelegramInitializeLegacyComponents.swift index e776147cae..6696dca29b 100644 --- a/TelegramUI/TelegramInitializeLegacyComponents.swift +++ b/TelegramUI/TelegramInitializeLegacyComponents.swift @@ -350,44 +350,56 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone } func navigationBarPallete() -> TGNavigationBarPallete! { + let theme: PresentationTheme if let account = legacyComponentsAccount { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let theme = presentationData.theme.rootController.navigationBar - return TGNavigationBarPallete(backgroundColor: theme.backgroundColor, separatorColor: theme.separatorColor, titleColor: theme.primaryTextColor, tintColor: theme.accentTextColor) + theme = presentationData.theme } else { - return nil + theme = defaultPresentationTheme } + let barTheme = theme.rootController.navigationBar + return TGNavigationBarPallete(backgroundColor: barTheme.backgroundColor, separatorColor: barTheme.separatorColor, titleColor: barTheme.primaryTextColor, tintColor: barTheme.accentTextColor) } func menuSheetPallete() -> TGMenuSheetPallete! { + let theme: PresentationTheme if let account = legacyComponentsAccount { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let theme = presentationData.theme.actionSheet - return TGMenuSheetPallete(dark: presentationData.theme.overallDarkAppearance, backgroundColor: theme.opaqueItemBackgroundColor, selectionColor: theme.opaqueItemHighlightedBackgroundColor, separatorColor: theme.opaqueItemSeparatorColor, accentColor: theme.controlAccentColor, destructiveColor: theme.destructiveActionTextColor, textColor: theme.primaryTextColor, secondaryTextColor: theme.secondaryTextColor, spinnerColor: theme.secondaryTextColor, badgeTextColor: theme.controlAccentColor, badgeImage: nil, cornersImage: generateStretchableFilledCircleImage(diameter: 11.0, color: nil, strokeColor: nil, strokeWidth: nil, backgroundColor: theme.opaqueItemBackgroundColor)) + theme = presentationData.theme } else { - return nil + theme = defaultPresentationTheme } + let sheetTheme = theme.actionSheet + + return TGMenuSheetPallete(dark: theme.overallDarkAppearance, backgroundColor: sheetTheme.opaqueItemBackgroundColor, selectionColor: sheetTheme.opaqueItemHighlightedBackgroundColor, separatorColor: sheetTheme.opaqueItemSeparatorColor, accentColor: sheetTheme.controlAccentColor, destructiveColor: sheetTheme.destructiveActionTextColor, textColor: sheetTheme.primaryTextColor, secondaryTextColor: sheetTheme.secondaryTextColor, spinnerColor: sheetTheme.secondaryTextColor, badgeTextColor: sheetTheme.controlAccentColor, badgeImage: nil, cornersImage: generateStretchableFilledCircleImage(diameter: 11.0, color: nil, strokeColor: nil, strokeWidth: nil, backgroundColor: sheetTheme.opaqueItemBackgroundColor)) } func mediaAssetsPallete() -> TGMediaAssetsPallete! { + let presentationTheme: PresentationTheme if let account = legacyComponentsAccount { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let theme = presentationData.theme.list - let navigationBar = presentationData.theme.rootController.navigationBar - return TGMediaAssetsPallete(dark: presentationData.theme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, barBackgroundColor: navigationBar.backgroundColor, barSeparatorColor: navigationBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.backgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationData.theme), maybeAccentColor: navigationBar.accentTextColor) + presentationTheme = presentationData.theme } else { - return nil + presentationTheme = defaultPresentationTheme } + + let theme = presentationTheme.list + let navigationBar = presentationTheme.rootController.navigationBar + + return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, barBackgroundColor: navigationBar.backgroundColor, barSeparatorColor: navigationBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.backgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor) } func checkButtonPallete() -> TGCheckButtonPallete! { + let presentationTheme: PresentationTheme if let account = legacyComponentsAccount { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let theme = presentationData.theme - return TGCheckButtonPallete(defaultBackgroundColor: theme.chat.bubble.selectionControlFillColor, accentBackgroundColor: theme.chat.bubble.selectionControlFillColor, defaultBorderColor: theme.chat.bubble.selectionControlBorderColor, mediaBorderColor: theme.chat.bubble.selectionControlBorderColor, chatBorderColor: theme.chat.bubble.selectionControlBorderColor, check: theme.chat.bubble.selectionControlForegroundColor, blueColor: theme.chat.bubble.selectionControlFillColor, barBackgroundColor: theme.chat.bubble.selectionControlFillColor) + presentationTheme = presentationData.theme } else { - return nil + presentationTheme = defaultPresentationTheme } + + let theme = presentationTheme + return TGCheckButtonPallete(defaultBackgroundColor: theme.chat.bubble.selectionControlFillColor, accentBackgroundColor: theme.chat.bubble.selectionControlFillColor, defaultBorderColor: theme.chat.bubble.selectionControlBorderColor, mediaBorderColor: theme.chat.bubble.selectionControlBorderColor, chatBorderColor: theme.chat.bubble.selectionControlBorderColor, check: theme.chat.bubble.selectionControlForegroundColor, blueColor: theme.chat.bubble.selectionControlFillColor, barBackgroundColor: theme.chat.bubble.selectionControlFillColor) } } diff --git a/TelegramUI/ThemeAutoNightSettingsController.swift b/TelegramUI/ThemeAutoNightSettingsController.swift index d5319d4a58..faddbe2088 100644 --- a/TelegramUI/ThemeAutoNightSettingsController.swift +++ b/TelegramUI/ThemeAutoNightSettingsController.swift @@ -287,7 +287,7 @@ private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, s case let .automatic(_, _, sunset, sunrise, localizedName): entries.append(.timeBasedAutomaticLocationValue(theme, strings.AutoNightTheme_UpdateLocation, localizedName)) if sunset != 0 || sunrise != 0 { - entries.append(.settingInfo(theme, strings.AutoNightTheme_LocationHelp(stringForMessageTimestamp(timestamp: sunrise, timeFormat: timeFormat, local: false), stringForMessageTimestamp(timestamp: sunset, timeFormat: timeFormat, local: false)).0)) + entries.append(.settingInfo(theme, strings.AutoNightTheme_LocationHelp(stringForMessageTimestamp(timestamp: sunset, timeFormat: timeFormat, local: false), stringForMessageTimestamp(timestamp: sunrise, timeFormat: timeFormat, local: false)).0)) } case let .manual(fromSeconds, toSeconds): entries.append(.timeBasedManualFrom(theme, strings.AutoNightTheme_ScheduledFrom, stringForMessageTimestamp(timestamp: fromSeconds, timeFormat: timeFormat, local: false))) @@ -313,7 +313,7 @@ private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, s private func roundTimeToDay(_ timestamp: Int32) -> Int32 { let calendar = Calendar.current - let offset = TimeZone.current.secondsFromGMT(for: Date()) + let offset = 0//TimeZone.current.secondsFromGMT(for: Date()) let components = calendar.dateComponents([.hour, .minute, .second], from: Date(timeIntervalSince1970: Double(timestamp + Int32(offset)))) return Int32(components.hour! * 60 * 60 + components.minute! * 60 + components.second!) } @@ -324,7 +324,7 @@ private func areSettingsValid(_ settings: AutomaticThemeSwitchSetting) -> Bool { return true case let .timeBased(setting): switch setting { - case let .automatic(latitude, longitude, sunset, sunrise, _): + case let .automatic(latitude, longitude, _, _, _): if !latitude.isZero || !longitude.isZero { return true } else { @@ -391,7 +391,7 @@ public func themeAutoNightSettingsController(account: Account) -> ViewController updateSettings { settings in var settings = settings if case let .timeBased(setting) = settings.trigger, case .automatic = setting { - let calculator = EDSunriseSet(date: Date(), timezone: TimeZone(secondsFromGMT: 0), latitude: location.0, longitude: location.1)! + let calculator = EDSunriseSet(date: Date(), timezone: TimeZone.current, latitude: location.0, longitude: location.1)! let sunset = roundTimeToDay(Int32(calculator.sunset.timeIntervalSince1970)) let sunrise = roundTimeToDay(Int32(calculator.sunrise.timeIntervalSince1970)) diff --git a/TelegramUI/TwoStepVerificationUnlockController.swift b/TelegramUI/TwoStepVerificationUnlockController.swift index c84061faee..aae0b58a6a 100644 --- a/TelegramUI/TwoStepVerificationUnlockController.swift +++ b/TelegramUI/TwoStepVerificationUnlockController.swift @@ -628,7 +628,10 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep resultItemNode.focus() } } - controller.didAppear = { + controller.didAppear = { firstTime in + if !firstTime { + return + } initialFocusImpl?() } diff --git a/TelegramUI/UserInfoController.swift b/TelegramUI/UserInfoController.swift index 1bbc6ffa22..c39837bf61 100644 --- a/TelegramUI/UserInfoController.swift +++ b/TelegramUI/UserInfoController.swift @@ -1270,8 +1270,8 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll } } - controller.didAppear = { [weak controller] in - guard let controller = controller else { + controller.didAppear = { [weak controller] firstTime in + guard let controller = controller, firstTime else { return } diff --git a/TelegramUI/UsernameSetupController.swift b/TelegramUI/UsernameSetupController.swift index 4d987d0ffa..25169eb0c2 100644 --- a/TelegramUI/UsernameSetupController.swift +++ b/TelegramUI/UsernameSetupController.swift @@ -8,10 +8,12 @@ private final class UsernameSetupControllerArguments { let account: Account let updatePublicLinkText: (String?, String) -> Void + let shareLink: () -> Void - init(account: Account, updatePublicLinkText: @escaping (String?, String) -> Void) { + init(account: Account, updatePublicLinkText: @escaping (String?, String) -> Void, shareLink: @escaping () -> Void) { self.account = account self.updatePublicLinkText = updatePublicLinkText + self.shareLink = shareLink } } @@ -20,7 +22,7 @@ private enum UsernameSetupSection: Int32 { } private enum UsernameSetupEntry: ItemListNodeEntry { - case editablePublicLink(PresentationTheme, String?, String) + case editablePublicLink(PresentationTheme, String, String?, String) case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String) case publicLinkInfo(PresentationTheme, String) @@ -44,8 +46,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry { static func ==(lhs: UsernameSetupEntry, rhs: UsernameSetupEntry) -> Bool { switch lhs { - case let .editablePublicLink(lhsTheme, lhsCurrentText, lhsText): - if case let .editablePublicLink(rhsTheme, rhsCurrentText, rhsText) = rhs, lhsTheme === rhsTheme, lhsCurrentText == rhsCurrentText, lhsText == rhsText { + case let .editablePublicLink(lhsTheme, lhsPrefix, lhsCurrentText, lhsText): + if case let .editablePublicLink(rhsTheme, rhsPrefix, rhsCurrentText, rhsText) = rhs, lhsTheme === rhsTheme, lhsPrefix == rhsPrefix, lhsCurrentText == rhsCurrentText, lhsText == rhsText { return true } else { return false @@ -71,14 +73,18 @@ private enum UsernameSetupEntry: ItemListNodeEntry { func item(_ arguments: UsernameSetupControllerArguments) -> ListViewItem { switch self { - case let .editablePublicLink(theme, currentText, text): - return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .username, sectionId: self.section, textUpdated: { updatedText in + case let .editablePublicLink(theme, prefix, currentText, text): + return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .username, spacing: 10.0, sectionId: self.section, textUpdated: { updatedText in arguments.updatePublicLinkText(currentText, updatedText) }, action: { }) case let .publicLinkInfo(theme, text): - return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section) + return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section, linkAction: { action in + if case .tap = action { + arguments.shareLink() + } + }) case let .publicLinkStatus(theme, _, status, text): var displayActivity = false let string: NSAttributedString @@ -160,7 +166,7 @@ private func usernameSetupControllerEntries(presentationData: PresentationData, } } - entries.append(.editablePublicLink(presentationData.theme, peer.addressName, currentAddressName)) + entries.append(.editablePublicLink(presentationData.theme, presentationData.strings.Username_Title, peer.addressName, currentAddressName)) if let status = state.addressNameValidationStatus { let statusText: String switch status { @@ -187,7 +193,12 @@ private func usernameSetupControllerEntries(presentationData: PresentationData, } entries.append(.publicLinkStatus(presentationData.theme, currentAddressName, status, statusText)) } - entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Username_Help)) + + var infoText = presentationData.strings.Username_Help + infoText += "\n\n" + let hintText = presentationData.strings.Username_LinkHint(currentAddressName.replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "")).0.replacingOccurrences(of: "]", with: "]()") + infoText += hintText + entries.append(.publicLinkInfo(presentationData.theme, infoText)) } return entries @@ -201,6 +212,7 @@ public func usernameSetupController(account: Account) -> ViewController { } var dismissImpl: (() -> Void)? + var presentControllerImpl: ((ViewController, Any?) -> Void)? let actionsDisposable = DisposableSet() @@ -233,10 +245,25 @@ public func usernameSetupController(account: Account) -> ViewController { } })) } + }, shareLink: { + let _ = (account.postbox.loadedPeerWithId(account.peerId) + |> take(1) + |> deliverOnMainQueue).start(next: { peer in + var currentAddressName: String = peer.addressName ?? "" + updateState { state in + if let current = state.editingPublicLinkText { + currentAddressName = current + } + return state + } + if !currentAddressName.isEmpty { + presentControllerImpl?(ShareController(account: account, subject: .url("https://t.me/\(currentAddressName)")), nil) + } + }) }) let peerView = account.viewTracker.peerView(account.peerId) - |> deliverOnMainQueue + |> deliverOnMainQueue let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get() |> deliverOnMainQueue, peerView) |> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState, UsernameSetupEntry.ItemGenerationArguments)) in @@ -306,6 +333,9 @@ public func usernameSetupController(account: Account) -> ViewController { controller?.view.endEditing(true) controller?.dismiss() } + presentControllerImpl = { [weak controller] c, a in + controller?.present(c, in: .window(.root), with: a) + } return controller }