From 88cc91a43c9fa943108b3d399002c7e2c0524673 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sun, 8 Mar 2020 22:30:22 +0400 Subject: [PATCH] Filter improvements and suggested filters --- Makefile | 15 +- Telegram/BUILD | 19 +- build-system/generate-xcode-project.sh | 1 + build-system/prepare-build-variables.sh | 2 + .../Sources/ChatListController.swift | 106 +++++++-- .../Sources/ChatListControllerNode.swift | 21 +- .../ChatListFilterPresetListController.swift | 216 +++++++++++++----- .../ChatListFilterTabContainerNode.swift | 58 +++-- .../Sources/Node/ChatListNode.swift | 2 +- .../Sources/Node/ChatListViewTransition.swift | 15 +- submodules/Display/Source/ListView.swift | 2 +- .../Navigation/NavigationModalContainer.swift | 6 + .../MergeLists/Sources/MergeLists.swift | 35 ++- .../SettingsUI/Sources/DebugController.swift | 1 + .../Sources/SettingsController.swift | 3 +- submodules/SyncCore/Sources/Namespaces.swift | 7 + submodules/TelegramCore/Sources/Account.swift | 2 +- .../TelegramCore/Sources/AccountManager.swift | 1 + .../Sources/ChatListFiltering.swift | 124 +++++++++- .../Animations/ChatListFilterEmpty.tgs | Bin 8843 -> 6752 bytes .../TooltipUI/Sources/TooltipScreen.swift | 8 +- 21 files changed, 510 insertions(+), 134 deletions(-) diff --git a/Makefile b/Makefile index a9c1e10f33..a78940e107 100644 --- a/Makefile +++ b/Makefile @@ -405,13 +405,24 @@ bazel_app_arm64: --output_groups=+dsyms \ --verbose_failures -bazel_project: kill_xcode +bazel_prepare_development_build: + APP_VERSION="${APP_VERSION}" \ + BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \ + build-system/prepare-build.sh development + +bazel_project: kill_xcode bazel_prepare_development_build APP_VERSION="${APP_VERSION}" \ BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \ build-system/generate-xcode-project.sh -bazel_soft_project: +bazel_soft_project: bazel_prepare_development_build APP_VERSION="${APP_VERSION}" \ BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \ build-system/generate-xcode-project.sh +bazel_opt_project: bazel_prepare_development_build + APP_VERSION="${APP_VERSION}" \ + BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \ + GENERATE_OPT_PROJECT=1 \ + build-system/generate-xcode-project.sh + diff --git a/Telegram/BUILD b/Telegram/BUILD index b757fa8d57..d438aecce4 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -27,6 +27,7 @@ load( "telegram_version", "telegram_bundle_id", "telegram_aps_environment", + "telegram_team_id", ) config_setting( @@ -231,6 +232,11 @@ official_apple_pay_merchants = [ "merchant.telegram.tranzzo.test", ] +official_bundle_ids = [ + "ph.telegra.Telegraph", + "org.telegram.Telegram-iOS", +] + apple_pay_merchants = official_apple_pay_merchants if telegram_bundle_id == "ph.telegra.Telegraph" else "" apple_pay_merchants_fragment = "" if apple_pay_merchants == "" else """ @@ -242,6 +248,12 @@ apple_pay_merchants_fragment = "" if apple_pay_merchants == "" else """ """ +official_unrestricted_voip_fragment = """ +com.apple.developer.pushkit.unrestricted-voip + +""" +unrestricted_voip_fragment = official_unrestricted_voip_fragment if telegram_bundle_id in official_bundle_ids else "" + telegram_entitlements_template = """ com.apple.developer.icloud-services @@ -265,15 +277,16 @@ telegram_entitlements_template = """ group.{telegram_bundle_id} - com.apple.developer.pushkit.unrestricted-voip - -""" + apple_pay_merchants_fragment + application-identifier + {telegram_team_id}.{telegram_bundle_id} +""" + apple_pay_merchants_fragment + unrestricted_voip_fragment plist_fragment( name = "TelegramEntitlements", extension = "entitlements", template = telegram_entitlements_template.format( telegram_bundle_id = telegram_bundle_id, + telegram_team_id = telegram_team_id, telegram_aps_environment = telegram_aps_environment, ) ) diff --git a/build-system/generate-xcode-project.sh b/build-system/generate-xcode-project.sh index 73826043c9..4cc7c0d8b1 100755 --- a/build-system/generate-xcode-project.sh +++ b/build-system/generate-xcode-project.sh @@ -20,6 +20,7 @@ EXPECTED_VARIABLES=(\ BUILD_NUMBER \ APP_VERSION \ BUNDLE_ID \ + DEVELOPMENT_TEAM \ API_ID \ API_HASH \ APP_CENTER_ID \ diff --git a/build-system/prepare-build-variables.sh b/build-system/prepare-build-variables.sh index fb098631a9..d88042f518 100755 --- a/build-system/prepare-build-variables.sh +++ b/build-system/prepare-build-variables.sh @@ -27,6 +27,7 @@ prepare_build_variables () { BUILD_NUMBER \ APP_VERSION \ BUNDLE_ID \ + DEVELOPMENT_TEAM \ API_ID \ API_HASH \ APP_CENTER_ID \ @@ -57,6 +58,7 @@ prepare_build_variables () { echo "telegram_version = \"$APP_VERSION\"" >> "$VARIABLES_PATH" echo "telegram_bundle_id = \"$BUNDLE_ID\"" >> "$VARIABLES_PATH" echo "telegram_api_id = \"$API_ID\"" >> "$VARIABLES_PATH" + echo "telegram_team_id = \"$DEVELOPMENT_TEAM\"" >> "$VARIABLES_PATH" echo "telegram_api_hash = \"$API_HASH\"" >> "$VARIABLES_PATH" echo "telegram_app_center_id = \"$APP_CENTER_ID\"" >> "$VARIABLES_PATH" echo "telegram_is_internal_build = \"$IS_INTERNAL_BUILD\"" >> "$VARIABLES_PATH" diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index b937c1e766..afb6f7776c 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -134,7 +134,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, private var presentationDataDisposable: Disposable? private let stateDisposable = MetaDisposable() - private var filterDisposable = MetaDisposable() + private let filterDisposable = MetaDisposable() + private let featuredFiltersDisposable = MetaDisposable() + private var processedFeaturedFilters = false private let isReorderingTabsValue = ValuePromise(false) @@ -298,8 +300,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, isRoot = true if isReorderingTabs { - let rightBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.reorderingDonePressed)) - strongSelf.navigationItem.rightBarButtonItem = rightBarButtonItem + strongSelf.navigationItem.rightBarButtonItem = nil } else { let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed)) rightBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.VoiceOver_Navigation_Compose @@ -307,7 +308,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } if isReorderingTabs { - strongSelf.navigationItem.leftBarButtonItem = nil + let leftBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.reorderingDonePressed)) + strongSelf.navigationItem.leftBarButtonItem = leftBarButtonItem } else { let editItem: UIBarButtonItem if state.editing { @@ -616,6 +618,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self.presentationDataDisposable?.dispose() self.stateDisposable.dispose() self.filterDisposable.dispose() + self.featuredFiltersDisposable.dispose() } private func updateThemeAndStrings() { @@ -999,6 +1002,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, self.didAppear = true + self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(true) + guard case .root = self.groupId else { return } @@ -1088,14 +1093,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, _ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.context.account.postbox, languageCode: suggestedLocalization.languageCode).start() } })) - - Queue.mainQueue().after(1.0, { - if let _ = self.validLayout, let parentController = self.parent as? TabBarController, let sourceFrame = parentController.frameForControllerTab(controller: self) { - let absoluteFrame = sourceFrame - //TODO:localize - //parentController.present(TooltipScreen(text: "Hold the Chats icon for quick access to the list of chat filters.", location: CGPoint(x: absoluteFrame.midX - 14.0, y: absoluteFrame.minY - 8.0)), in: .current) - } - }) } self.chatListDisplayNode.containerNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in @@ -1117,17 +1114,61 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return true }) } + + if !self.processedFeaturedFilters { + self.featuredFiltersDisposable.set(( + self.context.account.postbox.transaction { transaction -> ChatListFiltersFeaturedState? in + return transaction.getPreferencesEntry(key: PreferencesKeys.chatListFiltersFeaturedState) as? ChatListFiltersFeaturedState + } + |> delay(1.0, queue: .mainQueue()) + |> deliverOnMainQueue + ).start(next: { [weak self] featuredState in + guard let strongSelf = self, let featuredState = featuredState else { + return + } + strongSelf.processedFeaturedFilters = true + if !featuredState.isSeen && !featuredState.filters.isEmpty { + let _ = (strongSelf.context.account.postbox.transaction { transaction -> Bool in + if let state = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState { + return !state.filters.isEmpty + } else { + return false + } + } + |> deliverOnMainQueue).start(next: { hasFilters in + guard let strongSelf = self else { + return + } + if let _ = strongSelf.validLayout, let parentController = strongSelf.parent as? TabBarController, let sourceFrame = parentController.frameForControllerTab(controller: strongSelf) { + let absoluteFrame = sourceFrame + //TODO:localize + let text: String + if hasFilters { + text = "Hold on 'Chats' to edit folders and switch between views." + } else { + text = "Hold to organize your chats with folders." + } + parentController.present(TooltipScreen(text: text, location: CGPoint(x: absoluteFrame.midX - 14.0, y: absoluteFrame.minY - 8.0)), in: .current) + } + }) + } + })) + } } override public func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(false) + self.forEachController({ controller in if let controller = controller as? UndoOverlayController { controller.dismissWithCommitAction() } return true }) + + self.featuredFiltersDisposable.set(nil) } override public func viewDidDisappear(_ animated: Bool) { @@ -1368,12 +1409,33 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - let _ = updateChatListFilterSettingsInteractively(postbox: strongSelf.context.account.postbox, { settings in - var settings = settings - settings.filters = settings.filters.filter({ $0.id != id }) - return settings - }).start() - let _ = replaceRemoteChatListFilters(account: strongSelf.context.account).start() + + let commit: () -> Void = { + guard let strongSelf = self else { + return + } + + if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id == id { + if strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing { + strongSelf.donePressed() + } + } + + let _ = updateChatListFilterSettingsInteractively(postbox: strongSelf.context.account.postbox, { settings in + var settings = settings + settings.filters = settings.filters.filter({ $0.id != id }) + return settings + }).start() + let _ = replaceRemoteChatListFilters(account: strongSelf.context.account).start() + } + + if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id == id { + strongSelf.chatListDisplayNode.containerNode.switchToFilter(id: .all, completion: { + commit() + }) + } else { + commit() + } }) ]), ActionSheetItemGroup(items: [ @@ -2232,7 +2294,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, } private func openFilterSettings() { - self.push(chatListFilterPresetListController(context: self.context, updated: { _ in + self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(false) + self.push(chatListFilterPresetListController(context: self.context, mode: .modal, dismissed: { [weak self] in + self?.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(true) })) } @@ -2250,6 +2314,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return } + let _ = markChatListFeaturedFiltersAsSeen(postbox: strongSelf.context.account.postbox).start() + let (_, filterItems) = filterItemsAndTotalCount var items: [ContextMenuItem] = [] @@ -2260,7 +2326,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in })) + strongSelf.openFilterSettings() }) }))) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 446d9d5423..843763be3c 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -89,6 +89,8 @@ private final class ChatListContainerItemNode: ASDisplayNode { emptyNode.frame = emptyNodeFrame emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate) } + emptyNode.alpha = 0.0 + transition.updateAlpha(node: emptyNode, alpha: 1.0) } if !isLoading { strongSelf.becameEmpty(filter) @@ -149,6 +151,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private var disableItemNodeOperationsWhileAnimating: Bool = false private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, isReorderingFilters: Bool, isEditing: Bool)? + private var enableAdjacentFilterLoading: Bool = false + private var panRecognizer: InteractiveTransitionGestureRecognizer? private let _ready = Promise() @@ -458,7 +462,17 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } - func switchToFilter(id: ChatListFilterTabEntryId) { + func updateEnableAdjacentFilterLoading(_ value: Bool) { + if value != self.enableAdjacentFilterLoading { + self.enableAdjacentFilterLoading = value + + if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + } + } + } + + func switchToFilter(id: ChatListFilterTabEntryId, completion: (() -> Void)? = nil) { guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout else { return } @@ -472,6 +486,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring) self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) + completion?() } else if self.pendingItemNode == nil { let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[index].filter, previewing: self.previewing, presentationData: self.presentationData, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) @@ -549,6 +564,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition, false) } + + completion?() })) } } @@ -574,7 +591,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { let id = self.availableFilters[i].id validNodeIds.append(id) - if self.itemNodes[id] == nil && !self.disableItemNodeOperationsWhileAnimating { + if self.itemNodes[id] == nil && self.enableAdjacentFilterLoading && !self.disableItemNodeOperationsWhileAnimating { let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[i].filter, previewing: self.previewing, presentationData: self.presentationData, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 092a2aaec1..8a2d3a7e3f 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -58,12 +58,20 @@ private enum ChatListFilterPresetListEntryStableId: Hashable { case listFooter } +private struct PresetIndex: Equatable { + let value: Int + + static func ==(lhs: PresetIndex, rhs: PresetIndex) -> Bool { + return true + } +} + private enum ChatListFilterPresetListEntry: ItemListNodeEntry { case screenHeader(String) case suggestedListHeader(String) - case suggestedPreset(index: Int, title: String, label: String, preset: ChatListFilterData) + case suggestedPreset(index: PresetIndex, title: String, label: String, preset: ChatListFilterData) case listHeader(String) - case preset(index: Int, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool) + case preset(index: PresetIndex, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool) case addItem(text: String, isEditing: Bool) case listFooter(String) @@ -85,7 +93,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { case .listHeader: return 100 case let .preset(preset): - return 101 + preset.index + return 101 + preset.index.value case .addItem: return 1000 case .listFooter: @@ -93,7 +101,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { case .suggestedListHeader: return 1002 case let .suggestedPreset(suggestedPreset): - return 1003 + suggestedPreset.index + return 1003 + suggestedPreset.index.value } } @@ -156,14 +164,30 @@ private struct ChatListFilterPresetListControllerState: Equatable { var revealedPreset: Int32? = nil } -private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filters: [(ChatListFilter, Int)], suggestedFilters: [(String, String, ChatListFilterData)], settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] { +private func filtersWithAppliedOrder(filters: [(ChatListFilter, Int)], order: [Int32]?) -> [(ChatListFilter, Int)] { + let sortedFilters: [(ChatListFilter, Int)] + if let updatedFilterOrder = order { + var updatedFilters: [(ChatListFilter, Int)] = [] + for id in updatedFilterOrder { + if let index = filters.firstIndex(where: { $0.0.id == id }) { + updatedFilters.append(filters[index]) + } + } + sortedFilters = updatedFilters + } else { + sortedFilters = filters + } + return sortedFilters +} + +private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filters: [(ChatListFilter, Int)], updatedFilterOrder: [Int32]?, suggestedFilters: [ChatListFeaturedFilter], settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] { var entries: [ChatListFilterPresetListEntry] = [] entries.append(.screenHeader("Create folders for different groups of chats and\nquickly switch between them.")) - let filteredSuggestedFilters = suggestedFilters.filter { _, _, data in + let filteredSuggestedFilters = suggestedFilters.filter { suggestedFilter in for (filter, _) in filters { - if filter.data == data { + if filter.data == suggestedFilter.data { return false } } @@ -171,8 +195,9 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present } entries.append(.listHeader("FOLDERS")) - for (filter, chatCount) in filters { - entries.append(.preset(index: entries.count, title: filter.title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing)) + + for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) { + entries.append(.preset(index: PresetIndex(value: entries.count), title: filter.title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing)) } if filters.count < 10 { entries.append(.addItem(text: "Create New Folder", isEditing: state.isEditing)) @@ -181,15 +206,20 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present if !filteredSuggestedFilters.isEmpty { entries.append(.suggestedListHeader("RECOMMENDED FOLDERS")) - for (title, label, data) in filteredSuggestedFilters { - entries.append(.suggestedPreset(index: entries.count, title: title, label: label, preset: data)) + for filter in filteredSuggestedFilters { + entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title, label: filter.description, preset: filter.data)) } } return entries } -public func chatListFilterPresetListController(context: AccountContext, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController { +public enum ChatListFilterPresetListControllerMode { + case `default` + case modal +} + +public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, dismissed: (() -> Void)? = nil) -> ViewController { let initialState = ChatListFilterPresetListControllerState() let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -208,14 +238,12 @@ public func chatListFilterPresetListController(context: AccountContext, updated: return settings }) |> deliverOnMainQueue).start(next: { settings in - updated(settings.filters) - let _ = replaceRemoteChatListFilters(account: context.account).start() }) }, openPreset: { preset in - pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: updated)) + pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: { _ in })) }, addNew: { - pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: updated)) + pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: { _ in })) }, setItemWithRevealedOptions: { preset, fromPreset in updateState { state in var state = state @@ -233,22 +261,23 @@ public func chatListFilterPresetListController(context: AccountContext, updated: return settings }) |> deliverOnMainQueue).start(next: { settings in - updated(settings.filters) - let _ = replaceRemoteChatListFilters(account: context.account).start() }) }) let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:]) - let filtersWithCounts = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters]) + let filtersWithCountsSignal = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters]) |> map { preferences -> [ChatListFilter] in let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default return filtersState.filters } |> distinctUntilChanged |> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in - return context.account.postbox.transaction { transaction -> [(ChatListFilter, Int)] in + return .single(filters.map { filter -> (ChatListFilter, Int) in + return (filter, 0) + }) + /*return context.account.postbox.transaction { transaction -> [(ChatListFilter, Int)] in return filters.map { filter -> (ChatListFilter, Int) in let count: Int if let cachedValue = chatCountCache.with({ dict -> Int? in @@ -265,36 +294,93 @@ public func chatListFilterPresetListController(context: AccountContext, updated: } return (filter, count) } - } + }*/ } - let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings]) + let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState]) + |> map { preferences -> [ChatListFeaturedFilter] in + guard let state = preferences.values[PreferencesKeys.chatListFiltersFeaturedState] as? ChatListFiltersFeaturedState else { + return [] + } + return state.filters + } + |> distinctUntilChanged - let suggestedFilters: [(String, String, ChatListFilterData)] = [ - ("Unread", "All unread chats", ChatListFilterData(categories: .all, excludeMuted: false, excludeRead: true, excludeArchived: false, includePeers: [], excludePeers: [])), - ("Personal", "Exclude groups and channels", ChatListFilterData(categories: ChatListFilterPeerCategories.all.subtracting([.groups, .channels]), excludeMuted: false, excludeRead: false, excludeArchived: false, includePeers: [], excludePeers: [])), - ] + let filtersWithCounts = Promise<[(ChatListFilter, Int)]>() + filtersWithCounts.set(filtersWithCountsSignal) + + let updatedFilterOrder = Promise<[Int32]?>(nil) + + let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings]) let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), - filtersWithCounts, - preferences + filtersWithCounts.get(), + preferences, + updatedFilterOrder.get(), + featuredFilters ) - |> map { presentationData, state, filtersWithCounts, preferences -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, filtersWithCountsValue, preferences, updatedFilterOrderValue, suggestedFilters -> (ItemListControllerState, (ItemListNodeState, Any)) in let filterSettings = preferences.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings ?? ChatListFilterSettings.default - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: { - dismissImpl?() - }) + let leftNavigationButton: ItemListNavigationButton? + switch mode { + case .default: + leftNavigationButton = nil + case .modal: + leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: { + dismissImpl?() + }) + } let rightNavigationButton: ItemListNavigationButton if state.isEditing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { - updateState { state in - var state = state - state.isEditing = false - return state - } - }) + rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + let _ = (updatedFilterOrder.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak updatedFilterOrder] updatedFilterOrderValue in + if let updatedFilterOrderValue = updatedFilterOrderValue { + let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { filtersState in + var filtersState = filtersState + + var updatedFilters: [ChatListFilter] = [] + for id in updatedFilterOrderValue { + if let index = filtersState.filters.firstIndex(where: { $0.id == id }) { + updatedFilters.append(filtersState.filters[index]) + } + } + for filter in filtersState.filters { + if !updatedFilters.contains(where: { $0.id == filter.id }) { + updatedFilters.append(filter) + } + } + + filtersState.filters = updatedFilters + + return filtersState + }) + |> deliverOnMainQueue).start(next: { _ in + filtersWithCounts.set(filtersWithCountsSignal) + let _ = (filtersWithCounts.get() + |> take(1) + |> deliverOnMainQueue).start(next: { _ in + updatedFilterOrder?.set(.single(nil)) + + updateState { state in + var state = state + state.isEditing = false + return state + } + }) + }) + } else { + updateState { state in + var state = state + state.isEditing = false + return state + } + } + }) + }) } else { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: { updateState { state in @@ -306,7 +392,7 @@ public func chatListFilterPresetListController(context: AccountContext, updated: } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Folders"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCounts, suggestedFilters: suggestedFilters, settings: filterSettings), style: .blocks, animateChanges: true) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCountsValue, updatedFilterOrder: updatedFilterOrderValue, suggestedFilters: suggestedFilters, settings: filterSettings), style: .blocks, animateChanges: true) return (controllerState, (listState, arguments)) } @@ -314,9 +400,15 @@ public func chatListFilterPresetListController(context: AccountContext, updated: } let controller = ItemListController(context: context, state: signal) - controller.navigationPresentation = .modal - controller.willDisappear = { _ in + switch mode { + case .default: + controller.navigationPresentation = .default + case .modal: + controller.navigationPresentation = .modal + } + controller.didDisappear = { _ in let _ = replaceRemoteChatListFilters(account: context.account).start() + dismissed?() } pushControllerImpl = { [weak controller] c in controller?.push(c) @@ -347,36 +439,46 @@ public func chatListFilterPresetListController(context: AccountContext, updated: afterAll = true } - return updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { filtersState in - var filtersState = filtersState - if let index = filtersState.filters.firstIndex(where: { $0.id == fromFilter.preset.id }) { - filtersState.filters.remove(at: index) + return combineLatest( + updatedFilterOrder.get() |> take(1), + filtersWithCounts.get() |> take(1) + ) + |> mapToSignal { updatedFilterOrderValue, filtersWithCountsValue -> Signal in + var filters = filtersWithAppliedOrder(filters: filtersWithCountsValue, order: updatedFilterOrderValue).map { $0.0 } + let initialOrder = filters.map { $0.id } + + if let index = filters.firstIndex(where: { $0.id == fromFilter.preset.id }) { + filters.remove(at: index) } if let referenceFilter = referenceFilter { var inserted = false - for i in 0 ..< filtersState.filters.count { - if filtersState.filters[i].id == referenceFilter.id { + for i in 0 ..< filters.count { + if filters[i].id == referenceFilter.id { if fromIndex < toIndex { - filtersState.filters.insert(fromFilter.preset, at: i + 1) + filters.insert(fromFilter.preset, at: i + 1) } else { - filtersState.filters.insert(fromFilter.preset, at: i) + filters.insert(fromFilter.preset, at: i) } inserted = true break } } if !inserted { - filtersState.filters.append(fromFilter.preset) + filters.append(fromFilter.preset) } } else if beforeAll { - filtersState.filters.insert(fromFilter.preset, at: 0) + filters.insert(fromFilter.preset, at: 0) } else if afterAll { - filtersState.filters.append(fromFilter.preset) + filters.append(fromFilter.preset) + } + + let updatedOrder = filters.map { $0.id } + if initialOrder != updatedOrder { + updatedFilterOrder.set(.single(updatedOrder)) + return .single(true) + } else { + return .single(false) } - return filtersState - }) - |> map { _ -> Bool in - return false } }) diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift index 91a0a72887..e7d228ae38 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift @@ -205,10 +205,10 @@ private final class ItemNode: ASDisplayNode { } func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> (width: CGFloat, shortWidth: CGFloat) { - let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) + let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - let shortTitleSize = self.shortTitleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) + let shortTitleSize = self.shortTitleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) self.shortTitleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - shortTitleSize.height) / 2.0)), size: shortTitleSize) if let deleteButtonNode = self.deleteButtonNode { @@ -609,10 +609,12 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { for filter in reorderedFilters { let itemNode: ItemNode + var itemNodeTransition = transition var wasAdded = false if let current = self.itemNodes[filter.id] { itemNode = current } else { + itemNodeTransition = .immediate wasAdded = true itemNode = ItemNode(pressed: { [weak self] in self?.tabSelected?(filter.id) @@ -646,7 +648,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { if !wasAdded && (itemNode.unreadCount != 0) != (unreadCount != 0) { badgeAnimations[filter.id] = (unreadCount != 0) ? .in : .out } - itemNode.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, isNoFilter: isNoFilter, isSelected: selectedFilter == filter.id, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: transition) + itemNode.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, isNoFilter: isNoFilter, isSelected: selectedFilter == filter.id, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition) } var removeKeys: [ChatListFilterTabEntryId] = [] for (id, _) in self.itemNodes { @@ -672,10 +674,12 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { continue } let wasAdded = itemNode.supernode == nil + var itemNodeTransition = transition if wasAdded { + itemNodeTransition = .immediate self.scrollNode.addSubnode(itemNode) } - let (paneNodeWidth, paneNodeShortWidth) = itemNode.updateLayout(height: size.height, transition: transition) + let (paneNodeWidth, paneNodeShortWidth) = itemNode.updateLayout(height: size.height, transition: itemNodeTransition) let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) let paneNodeShortSize = CGSize(width: paneNodeShortWidth, height: size.height) tabSizes.append((filter.id, paneNodeSize, paneNodeShortSize, itemNode, wasAdded)) @@ -709,27 +713,32 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { for i in 0 ..< tabSizes.count { let (itemId, paneNodeLongSize, paneNodeShortSize, paneNode, wasAdded) = tabSizes[i] + var itemNodeTransition = transition + if wasAdded { + itemNodeTransition = .immediate + } + let useShortTitle = itemId == .all && useShortTitles let paneNodeSize = useShortTitle ? paneNodeShortSize : paneNodeLongSize let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) if itemId == self.reorderingItem, let (initial, offset) = self.reorderingItemPosition { - transition.updateSublayerTransformScale(node: paneNode, scale: 1.2) - transition.updateAlpha(node: paneNode, alpha: 0.9) - transition.updateFrameAdditive(node: paneNode, frame: CGRect(origin: CGPoint(x: initial + offset, y: paneFrame.minY), size: paneFrame.size)) + itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.2) + itemNodeTransition.updateAlpha(node: paneNode, alpha: 0.9) + itemNodeTransition.updateFrameAdditive(node: paneNode, frame: CGRect(origin: CGPoint(x: initial + offset, y: paneFrame.minY), size: paneFrame.size)) } else { - transition.updateSublayerTransformScale(node: paneNode, scale: 1.0) - transition.updateAlpha(node: paneNode, alpha: 1.0) + itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.0) + itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0) if wasAdded { paneNode.frame = paneFrame paneNode.alpha = 0.0 - transition.updateAlpha(node: paneNode, alpha: 1.0) + itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0) } else { - transition.updateFrameAdditive(node: paneNode, frame: paneFrame) + itemNodeTransition.updateFrameAdditive(node: paneNode, frame: paneFrame) } } - paneNode.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0, useShortTitle: useShortTitle, transition: transition) + paneNode.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0, useShortTitle: useShortTitle, transition: itemNodeTransition) paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0) selectionFrames.append(paneFrame) @@ -774,14 +783,11 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { } transition.updateAlpha(node: self.selectedLineNode, alpha: isReordering && selectedFilter == .all ? 0.5 : 1.0) - //if !transitionFraction.isZero { - if let previousSelectedFrame = self.previousSelectedFrame, abs(previousSelectedFrame.offsetBy(dx: -previousScrollBounds.minX, dy: 0.0).midX - previousScrollBounds.width / 2.0) < 1.0 { - let previousContentOffsetX = max(0.0, min(previousContentWidth - self.scrollNode.bounds.width, floor(previousSelectedFrame.midX - self.scrollNode.bounds.width / 2.0))) - if abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0 { - focusOnSelectedFilter = true - } - } - //} + if let previousSelectedFrame = self.previousSelectedFrame { + let previousContentOffsetX = max(0.0, min(previousContentWidth - previousScrollBounds.width, floor(previousSelectedFrame.midX - previousScrollBounds.width / 2.0))) + focusOnSelectedFilter = abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0 + } + if focusOnSelectedFilter && self.reorderingItem == nil { let updatedBounds: CGRect if transitionFraction.isZero && selectedFilter == reorderedFilters.first?.id { @@ -795,17 +801,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { self.scrollNode.bounds = updatedBounds } transition.animateHorizontalOffsetAdditive(node: self.scrollNode, offset: previousScrollBounds.minX - self.scrollNode.bounds.minX) - /*else if false, !wasAdded, transitionFraction.isZero, let previousSelectedAbsFrame = self.previousSelectedAbsFrame { - let contentOffsetX: CGFloat - if previousScrollBounds.minX.isZero { - contentOffsetX = 0.0 - } else if previousScrollBounds.maxX == previousScrollBounds.width { - contentOffsetX = self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width - } else { - contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, selectedFrame.midX - previousSelectedAbsFrame.midX)) - } - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size)) - }*/ + self.previousSelectedAbsFrame = selectedFrame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0) self.previousSelectedFrame = selectedFrame } else { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index d16b4400f9..7b07dd7e77 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1474,7 +1474,7 @@ public final class ChatListNode: ListView { } var scrollToItem: ListViewScrollToItem? switch self.visibleContentOffset() { - case let .known(value) where abs(value) < navigationBarSearchContentHeight: + case let .known(value) where abs(value) < navigationBarSearchContentHeight - 1.0: if isNavigationHidden { scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift index c943dd0415..6aea40fc22 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift @@ -71,7 +71,6 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV var options: ListViewDeleteAndInsertOptions = [] var maxAnimatedInsertionIndex = -1 - var stationaryItemRange: (Int, Int)? var scrollToItem: ListViewScrollToItem? switch reason { @@ -168,7 +167,17 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV var fromEmptyView = false if let fromView = fromView { - if fromView.filteredEntries.isEmpty || fromView.filter != toView.filter { + var wasSingleHeader = false + if fromView.filteredEntries.count == 1, case .HeaderEntry = fromView.filteredEntries[0] { + wasSingleHeader = true + } + var isSingleHeader = false + if toView.filteredEntries.count == 1, case .HeaderEntry = toView.filteredEntries[0] { + isSingleHeader = true + } + if (wasSingleHeader || isSingleHeader), case .interactiveChanges = reason { + let _ = options.insert(.AnimateInsertion) + } else if fromView.filteredEntries.isEmpty || fromView.filter != toView.filter { options.remove(.AnimateInsertion) options.remove(.AnimateAlpha) fromEmptyView = true @@ -182,7 +191,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV adjustScrollToFirstItem = true } - subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, adjustScrollToFirstItem: adjustScrollToFirstItem)) + subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: nil, adjustScrollToFirstItem: adjustScrollToFirstItem)) subscriber.putCompletion() return EmptyDisposable diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 887ea553de..33ecb3d5e3 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -991,7 +991,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset, topItemFound { - completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset) + completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset - effectiveInsets.bottom - effectiveInsets.top) bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight) } diff --git a/submodules/Display/Source/Navigation/NavigationModalContainer.swift b/submodules/Display/Source/Navigation/NavigationModalContainer.swift index 0447d6eb5b..58062e6f8f 100644 --- a/submodules/Display/Source/Navigation/NavigationModalContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationModalContainer.swift @@ -433,6 +433,12 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: self.dim, alpha: 0.0, beginWithCurrentState: true) positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0 + self.bounds.height), beginWithCurrentState: true, completion: { [weak self] _ in + guard let strongSelf = self else { + return + } + for controller in strongSelf.container.controllers { + controller.viewWillDisappear(transition.isAnimated) + } completion() }) return positionTransition diff --git a/submodules/MergeLists/Sources/MergeLists.swift b/submodules/MergeLists/Sources/MergeLists.swift index 5b366bba3a..4a12a6da54 100644 --- a/submodules/MergeLists/Sources/MergeLists.swift +++ b/submodules/MergeLists/Sources/MergeLists.swift @@ -215,15 +215,36 @@ public func mergeListsStableWithUpdates(leftList: [T], rightList: [T], isLess for item in rightList { rightStableIds.append(getId(item)) } - if Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds { - /*var i = 0 - var j = 0 - while true { + if Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds && !allUpdated { + var updatedItems: [(T, AnyHashable)] = [] + for i in 0 ..< leftList.count { if getId(leftList[i]) != getId(rightList[i]) { - + removeIndices.append(i) + } else { + updatedItems.append((leftList[i], getId(leftList[i]))) } - }*/ - print("order changed") + } + var i = 0 + while i < rightList.count { + if updatedItems[i].1 != getId(rightList[i]) { + updatedItems.insert((rightList[i], getId(rightList[i])), at: i) + var previousIndex: Int? + for k in 0 ..< leftList.count { + if getId(leftList[k]) == getId(rightList[i]) { + previousIndex = k + break + } + } + assert(previousIndex != nil) + insertItems.append((i, rightList[i], previousIndex)) + } else { + if !isEqual(updatedItems[i].0, rightList[i]) { + updatedIndices.append((i, rightList[i], i)) + } + } + i += 1 + } + return (removeIndices, insertItems, updatedIndices) } var currentList = leftList diff --git a/submodules/SettingsUI/Sources/DebugController.swift b/submodules/SettingsUI/Sources/DebugController.swift index 2266898b90..1cc961b171 100644 --- a/submodules/SettingsUI/Sources/DebugController.swift +++ b/submodules/SettingsUI/Sources/DebugController.swift @@ -391,6 +391,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { if let context = arguments.context { let _ = (context.account.postbox.transaction { transaction -> Void in transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedPollResults) + unmarkChatListFeaturedFiltersAsSeen(transaction: transaction) }).start() } }) diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index cbc0798ea0..1cdee54516 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -1263,7 +1263,8 @@ public func settingsController(context: AccountContext, accountManager: AccountM let _ = (contextValue.get() |> deliverOnMainQueue |> take(1)).start(next: { context in - pushControllerImpl?(chatListFilterPresetListController(context: context, updated: { _ in })) + let controller = chatListFilterPresetListController(context: context, mode: .default) + pushControllerImpl?(controller) }) }) diff --git a/submodules/SyncCore/Sources/Namespaces.swift b/submodules/SyncCore/Sources/Namespaces.swift index 17c85e1cf2..a620c1aec3 100644 --- a/submodules/SyncCore/Sources/Namespaces.swift +++ b/submodules/SyncCore/Sources/Namespaces.swift @@ -208,6 +208,7 @@ private enum PreferencesKeyValues: Int32 { case contentSettings = 19 case chatListFilters = 20 case peersNearby = 21 + case chatListFiltersFeaturedState = 22 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -330,6 +331,12 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.peersNearby.rawValue) return key }() + + public static let chatListFiltersFeaturedState: ValueBoxKey = { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.chatListFiltersFeaturedState.rawValue) + return key + }() } private enum SharedDataKeyValues: Int32 { diff --git a/submodules/TelegramCore/Sources/Account.swift b/submodules/TelegramCore/Sources/Account.swift index be82e24425..4026a65b89 100644 --- a/submodules/TelegramCore/Sources/Account.swift +++ b/submodules/TelegramCore/Sources/Account.swift @@ -1044,7 +1044,7 @@ public class Account { self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) - self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network).start()) + self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network)) let importantBackgroundOperations: [Signal] = [ managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] }, diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index 855e3aa9e2..075e315698 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -155,6 +155,7 @@ private var declaredEncodables: Void = { declareEncodable(ChatListFiltersState.self, f: { ChatListFiltersState(decoder: $0) }) declareEncodable(PeersNearbyState.self, f: { PeersNearbyState(decoder: $0) }) declareEncodable(TelegramMediaDice.self, f: { TelegramMediaDice(decoder: $0) }) + declareEncodable(ChatListFiltersFeaturedState.self, f: { ChatListFiltersFeaturedState(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/ChatListFiltering.swift b/submodules/TelegramCore/Sources/ChatListFiltering.swift index 0db9b496b6..c673996a19 100644 --- a/submodules/TelegramCore/Sources/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/ChatListFiltering.swift @@ -329,8 +329,11 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa } } -func managedChatListFilters(postbox: Postbox, network: Network) -> Signal { - return requestChatListFilters(postbox: postbox, network: network) +func managedChatListFilters(postbox: Postbox, network: Network) -> Disposable { + let disposables = DisposableSet() + disposables.add(updateChatListFeaturedFilters(postbox: postbox, network: network).start()) + + disposables.add((requestChatListFilters(postbox: postbox, network: network) |> `catch` { _ -> Signal<[ChatListFilter], NoError> in return .complete() } @@ -343,7 +346,9 @@ func managedChatListFilters(postbox: Postbox, network: Network) -> Signal ignoreValues - } + }).start()) + + return disposables } public func replaceRemoteChatListFilters(account: Account) -> Signal { @@ -461,3 +466,116 @@ public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @es } } +public struct ChatListFeaturedFilter: PostboxCoding, Equatable { + public var title: String + public var description: String + public var data: ChatListFilterData + + fileprivate init( + title: String, + description: String, + data: ChatListFilterData + ) { + self.title = title + self.description = description + self.data = data + } + + public init(decoder: PostboxDecoder) { + self.title = decoder.decodeStringForKey("title", orElse: "") + self.description = decoder.decodeStringForKey("description", orElse: "") + self.data = ChatListFilterData( + categories: ChatListFilterPeerCategories(rawValue: decoder.decodeInt32ForKey("categories", orElse: 0)), + excludeMuted: decoder.decodeInt32ForKey("excludeMuted", orElse: 0) != 0, + excludeRead: decoder.decodeInt32ForKey("excludeRead", orElse: 0) != 0, + excludeArchived: decoder.decodeInt32ForKey("excludeArchived", orElse: 0) != 0, + includePeers: decoder.decodeInt64ArrayForKey("includePeers").map(PeerId.init), + excludePeers: decoder.decodeInt64ArrayForKey("excludePeers").map(PeerId.init) + ) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.title, forKey: "title") + encoder.encodeString(self.description, forKey: "description") + encoder.encodeInt32(self.data.categories.rawValue, forKey: "categories") + encoder.encodeInt32(self.data.excludeMuted ? 1 : 0, forKey: "excludeMuted") + encoder.encodeInt32(self.data.excludeRead ? 1 : 0, forKey: "excludeRead") + encoder.encodeInt32(self.data.excludeArchived ? 1 : 0, forKey: "excludeArchived") + encoder.encodeInt64Array(self.data.includePeers.map { $0.toInt64() }, forKey: "includePeers") + encoder.encodeInt64Array(self.data.excludePeers.map { $0.toInt64() }, forKey: "excludePeers") + } +} + +public struct ChatListFiltersFeaturedState: PreferencesEntry, Equatable { + public var filters: [ChatListFeaturedFilter] + public var isSeen: Bool + + fileprivate init(filters: [ChatListFeaturedFilter], isSeen: Bool) { + self.filters = filters + self.isSeen = isSeen + } + + public init(decoder: PostboxDecoder) { + self.filters = decoder.decodeObjectArrayWithDecoderForKey("filters") + self.isSeen = decoder.decodeInt32ForKey("isSeen", orElse: 0) != 0 + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObjectArray(self.filters, forKey: "filters") + encoder.encodeInt32(self.isSeen ? 1 : 0, forKey: "isSeen") + } + + public func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? ChatListFiltersFeaturedState, self == to { + return true + } else { + return false + } + } +} + +public func markChatListFeaturedFiltersAsSeen(postbox: Postbox) -> Signal { + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFiltersFeaturedState, { entry in + guard var state = entry as? ChatListFiltersFeaturedState else { + return entry + } + state.isSeen = true + return state + }) + } + |> ignoreValues +} + +public func unmarkChatListFeaturedFiltersAsSeen(transaction: Transaction) { + transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFiltersFeaturedState, { entry in + guard var state = entry as? ChatListFiltersFeaturedState else { + return entry + } + state.isSeen = false + return state + }) +} + +public func updateChatListFeaturedFilters(postbox: Postbox, network: Network) -> Signal { + return network.request(Api.functions.messages.getSuggestedDialogFilters()) + |> `catch` { _ -> Signal<[Api.DialogFilterSuggested], NoError> in + return .single([]) + } + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFiltersFeaturedState, { entry in + var state = entry as? ChatListFiltersFeaturedState ?? ChatListFiltersFeaturedState(filters: [], isSeen: false) + state.filters = result.map { item -> ChatListFeaturedFilter in + switch item { + case let .dialogFilterSuggested(filter, description): + let parsedFilter = ChatListFilter(apiFilter: filter) + return ChatListFeaturedFilter(title: parsedFilter.title, description: description, data: parsedFilter.data) + } + } + return state + }) + } + |> ignoreValues + } +} diff --git a/submodules/TelegramUI/Resources/Animations/ChatListFilterEmpty.tgs b/submodules/TelegramUI/Resources/Animations/ChatListFilterEmpty.tgs index ee8755f78aa98d8fbde8a37e64cafe776752ea84..2dd379959696b3135759896343bb55d43143f0cf 100644 GIT binary patch literal 6752 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 diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index 067b1a4570..bd4824836f 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -77,7 +77,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { transition.updateFrame(node: self.arrowNode, frame: CGRect(origin: CGPoint(x: floor(arrowCenterX - arrowSize.width / 2.0), y: backgroundFrame.height), size: arrowSize)) } - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: contentVerticalInset), size: textSize)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize)) transition.updateFrame(node: self.animatedStickerNode, frame: CGRect(origin: CGPoint(x: contentInset - animationInset, y: floor((backgroundHeight - animationSize.height) / 2.0) - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))) self.animatedStickerNode.updateLayout(size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0)) @@ -90,7 +90,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { eventIsPresses = event.type == .presses } if event.type == .touches || eventIsPresses { - self.requestDismiss() + //self.requestDismiss() return nil } } @@ -144,6 +144,10 @@ public final class TooltipScreen: ViewController { super.viewDidAppear(animated) self.controllerNode.animateIn() + + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5.0, execute: { [weak self] in + self?.dismiss() + }) } override public func loadDisplayNode() {