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 ee8755f78a..2dd3799596 100644
Binary files a/submodules/TelegramUI/Resources/Animations/ChatListFilterEmpty.tgs and b/submodules/TelegramUI/Resources/Animations/ChatListFilterEmpty.tgs differ
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() {