mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Filter improvements and suggested filters
This commit is contained in:
parent
ec69b23599
commit
88cc91a43c
15
Makefile
15
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
|
||||
|
||||
|
@ -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 """
|
||||
</array>
|
||||
"""
|
||||
|
||||
official_unrestricted_voip_fragment = """
|
||||
<key>com.apple.developer.pushkit.unrestricted-voip</key>
|
||||
<true/>
|
||||
"""
|
||||
unrestricted_voip_fragment = official_unrestricted_voip_fragment if telegram_bundle_id in official_bundle_ids else ""
|
||||
|
||||
telegram_entitlements_template = """
|
||||
<key>com.apple.developer.icloud-services</key>
|
||||
<array>
|
||||
@ -265,15 +277,16 @@ telegram_entitlements_template = """
|
||||
<array>
|
||||
<string>group.{telegram_bundle_id}</string>
|
||||
</array>
|
||||
<key>com.apple.developer.pushkit.unrestricted-voip</key>
|
||||
<true/>
|
||||
""" + apple_pay_merchants_fragment
|
||||
<key>application-identifier</key>
|
||||
<string>{telegram_team_id}.{telegram_bundle_id}</string>
|
||||
""" + 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,
|
||||
)
|
||||
)
|
||||
|
@ -20,6 +20,7 @@ EXPECTED_VARIABLES=(\
|
||||
BUILD_NUMBER \
|
||||
APP_VERSION \
|
||||
BUNDLE_ID \
|
||||
DEVELOPMENT_TEAM \
|
||||
API_ID \
|
||||
API_HASH \
|
||||
APP_CENTER_ID \
|
||||
|
@ -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"
|
||||
|
@ -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<Bool>(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()
|
||||
})
|
||||
})))
|
||||
|
||||
|
@ -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<Bool>()
|
||||
@ -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
|
||||
|
@ -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<Bool, NoError> 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
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -215,15 +215,36 @@ public func mergeListsStableWithUpdates<T>(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
|
||||
|
@ -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()
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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<AccountRunningImportantTasks, NoError>] = [
|
||||
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
||||
|
@ -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
|
||||
}()
|
||||
|
@ -329,8 +329,11 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|
||||
}
|
||||
}
|
||||
|
||||
func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
|
||||
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<Never,
|
||||
})
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}).start())
|
||||
|
||||
return disposables
|
||||
}
|
||||
|
||||
public func replaceRemoteChatListFilters(account: Account) -> Signal<Never, NoError> {
|
||||
@ -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<Never, NoError> {
|
||||
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<Never, NoError> {
|
||||
return network.request(Api.functions.messages.getSuggestedDialogFilters())
|
||||
|> `catch` { _ -> Signal<[Api.DialogFilterSuggested], NoError> in
|
||||
return .single([])
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, NoError> 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
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user