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 \
|
--output_groups=+dsyms \
|
||||||
--verbose_failures
|
--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}" \
|
APP_VERSION="${APP_VERSION}" \
|
||||||
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
||||||
build-system/generate-xcode-project.sh
|
build-system/generate-xcode-project.sh
|
||||||
|
|
||||||
bazel_soft_project:
|
bazel_soft_project: bazel_prepare_development_build
|
||||||
APP_VERSION="${APP_VERSION}" \
|
APP_VERSION="${APP_VERSION}" \
|
||||||
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
||||||
build-system/generate-xcode-project.sh
|
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_version",
|
||||||
"telegram_bundle_id",
|
"telegram_bundle_id",
|
||||||
"telegram_aps_environment",
|
"telegram_aps_environment",
|
||||||
|
"telegram_team_id",
|
||||||
)
|
)
|
||||||
|
|
||||||
config_setting(
|
config_setting(
|
||||||
@ -231,6 +232,11 @@ official_apple_pay_merchants = [
|
|||||||
"merchant.telegram.tranzzo.test",
|
"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 = official_apple_pay_merchants if telegram_bundle_id == "ph.telegra.Telegraph" else ""
|
||||||
|
|
||||||
apple_pay_merchants_fragment = "" if apple_pay_merchants == "" else """
|
apple_pay_merchants_fragment = "" if apple_pay_merchants == "" else """
|
||||||
@ -242,6 +248,12 @@ apple_pay_merchants_fragment = "" if apple_pay_merchants == "" else """
|
|||||||
</array>
|
</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 = """
|
telegram_entitlements_template = """
|
||||||
<key>com.apple.developer.icloud-services</key>
|
<key>com.apple.developer.icloud-services</key>
|
||||||
<array>
|
<array>
|
||||||
@ -265,15 +277,16 @@ telegram_entitlements_template = """
|
|||||||
<array>
|
<array>
|
||||||
<string>group.{telegram_bundle_id}</string>
|
<string>group.{telegram_bundle_id}</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.pushkit.unrestricted-voip</key>
|
<key>application-identifier</key>
|
||||||
<true/>
|
<string>{telegram_team_id}.{telegram_bundle_id}</string>
|
||||||
""" + apple_pay_merchants_fragment
|
""" + apple_pay_merchants_fragment + unrestricted_voip_fragment
|
||||||
|
|
||||||
plist_fragment(
|
plist_fragment(
|
||||||
name = "TelegramEntitlements",
|
name = "TelegramEntitlements",
|
||||||
extension = "entitlements",
|
extension = "entitlements",
|
||||||
template = telegram_entitlements_template.format(
|
template = telegram_entitlements_template.format(
|
||||||
telegram_bundle_id = telegram_bundle_id,
|
telegram_bundle_id = telegram_bundle_id,
|
||||||
|
telegram_team_id = telegram_team_id,
|
||||||
telegram_aps_environment = telegram_aps_environment,
|
telegram_aps_environment = telegram_aps_environment,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -20,6 +20,7 @@ EXPECTED_VARIABLES=(\
|
|||||||
BUILD_NUMBER \
|
BUILD_NUMBER \
|
||||||
APP_VERSION \
|
APP_VERSION \
|
||||||
BUNDLE_ID \
|
BUNDLE_ID \
|
||||||
|
DEVELOPMENT_TEAM \
|
||||||
API_ID \
|
API_ID \
|
||||||
API_HASH \
|
API_HASH \
|
||||||
APP_CENTER_ID \
|
APP_CENTER_ID \
|
||||||
|
@ -27,6 +27,7 @@ prepare_build_variables () {
|
|||||||
BUILD_NUMBER \
|
BUILD_NUMBER \
|
||||||
APP_VERSION \
|
APP_VERSION \
|
||||||
BUNDLE_ID \
|
BUNDLE_ID \
|
||||||
|
DEVELOPMENT_TEAM \
|
||||||
API_ID \
|
API_ID \
|
||||||
API_HASH \
|
API_HASH \
|
||||||
APP_CENTER_ID \
|
APP_CENTER_ID \
|
||||||
@ -57,6 +58,7 @@ prepare_build_variables () {
|
|||||||
echo "telegram_version = \"$APP_VERSION\"" >> "$VARIABLES_PATH"
|
echo "telegram_version = \"$APP_VERSION\"" >> "$VARIABLES_PATH"
|
||||||
echo "telegram_bundle_id = \"$BUNDLE_ID\"" >> "$VARIABLES_PATH"
|
echo "telegram_bundle_id = \"$BUNDLE_ID\"" >> "$VARIABLES_PATH"
|
||||||
echo "telegram_api_id = \"$API_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_api_hash = \"$API_HASH\"" >> "$VARIABLES_PATH"
|
||||||
echo "telegram_app_center_id = \"$APP_CENTER_ID\"" >> "$VARIABLES_PATH"
|
echo "telegram_app_center_id = \"$APP_CENTER_ID\"" >> "$VARIABLES_PATH"
|
||||||
echo "telegram_is_internal_build = \"$IS_INTERNAL_BUILD\"" >> "$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 var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
private let stateDisposable = MetaDisposable()
|
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)
|
private let isReorderingTabsValue = ValuePromise<Bool>(false)
|
||||||
|
|
||||||
@ -298,8 +300,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
isRoot = true
|
isRoot = true
|
||||||
|
|
||||||
if isReorderingTabs {
|
if isReorderingTabs {
|
||||||
let rightBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.reorderingDonePressed))
|
strongSelf.navigationItem.rightBarButtonItem = nil
|
||||||
strongSelf.navigationItem.rightBarButtonItem = rightBarButtonItem
|
|
||||||
} else {
|
} else {
|
||||||
let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed))
|
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
|
rightBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.VoiceOver_Navigation_Compose
|
||||||
@ -307,7 +308,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isReorderingTabs {
|
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 {
|
} else {
|
||||||
let editItem: UIBarButtonItem
|
let editItem: UIBarButtonItem
|
||||||
if state.editing {
|
if state.editing {
|
||||||
@ -616,6 +618,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
self.presentationDataDisposable?.dispose()
|
self.presentationDataDisposable?.dispose()
|
||||||
self.stateDisposable.dispose()
|
self.stateDisposable.dispose()
|
||||||
self.filterDisposable.dispose()
|
self.filterDisposable.dispose()
|
||||||
|
self.featuredFiltersDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateThemeAndStrings() {
|
private func updateThemeAndStrings() {
|
||||||
@ -999,6 +1002,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
|
|
||||||
self.didAppear = true
|
self.didAppear = true
|
||||||
|
|
||||||
|
self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(true)
|
||||||
|
|
||||||
guard case .root = self.groupId else {
|
guard case .root = self.groupId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1088,14 +1093,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
_ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.context.account.postbox, languageCode: suggestedLocalization.languageCode).start()
|
_ = 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
|
self.chatListDisplayNode.containerNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in
|
||||||
@ -1117,17 +1114,61 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
return true
|
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) {
|
override public func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(false)
|
||||||
|
|
||||||
self.forEachController({ controller in
|
self.forEachController({ controller in
|
||||||
if let controller = controller as? UndoOverlayController {
|
if let controller = controller as? UndoOverlayController {
|
||||||
controller.dismissWithCommitAction()
|
controller.dismissWithCommitAction()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.featuredFiltersDisposable.set(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func viewDidDisappear(_ animated: Bool) {
|
override public func viewDidDisappear(_ animated: Bool) {
|
||||||
@ -1368,12 +1409,33 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = updateChatListFilterSettingsInteractively(postbox: strongSelf.context.account.postbox, { settings in
|
|
||||||
var settings = settings
|
let commit: () -> Void = {
|
||||||
settings.filters = settings.filters.filter({ $0.id != id })
|
guard let strongSelf = self else {
|
||||||
return settings
|
return
|
||||||
}).start()
|
}
|
||||||
let _ = replaceRemoteChatListFilters(account: strongSelf.context.account).start()
|
|
||||||
|
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: [
|
ActionSheetItemGroup(items: [
|
||||||
@ -2232,7 +2294,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openFilterSettings() {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = markChatListFeaturedFiltersAsSeen(postbox: strongSelf.context.account.postbox).start()
|
||||||
|
|
||||||
let (_, filterItems) = filterItemsAndTotalCount
|
let (_, filterItems) = filterItemsAndTotalCount
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
@ -2260,7 +2326,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in }))
|
strongSelf.openFilterSettings()
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
|
||||||
|
@ -89,6 +89,8 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
|||||||
emptyNode.frame = emptyNodeFrame
|
emptyNode.frame = emptyNodeFrame
|
||||||
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate)
|
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
emptyNode.alpha = 0.0
|
||||||
|
transition.updateAlpha(node: emptyNode, alpha: 1.0)
|
||||||
}
|
}
|
||||||
if !isLoading {
|
if !isLoading {
|
||||||
strongSelf.becameEmpty(filter)
|
strongSelf.becameEmpty(filter)
|
||||||
@ -149,6 +151,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
private var disableItemNodeOperationsWhileAnimating: Bool = false
|
private var disableItemNodeOperationsWhileAnimating: Bool = false
|
||||||
private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, isReorderingFilters: Bool, isEditing: Bool)?
|
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 var panRecognizer: InteractiveTransitionGestureRecognizer?
|
||||||
|
|
||||||
private let _ready = Promise<Bool>()
|
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 {
|
guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -472,6 +486,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
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.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition)
|
||||||
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false)
|
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false)
|
||||||
|
completion?()
|
||||||
} else if self.pendingItemNode == nil {
|
} 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
|
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)
|
self?.filterBecameEmpty(filter)
|
||||||
@ -549,6 +564,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
|
|
||||||
strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition, false)
|
strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
completion?()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -574,7 +591,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
let id = self.availableFilters[i].id
|
let id = self.availableFilters[i].id
|
||||||
validNodeIds.append(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
|
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)
|
self?.filterBecameEmpty(filter)
|
||||||
}, emptyAction: { [weak self] filter in
|
}, emptyAction: { [weak self] filter in
|
||||||
|
@ -58,12 +58,20 @@ private enum ChatListFilterPresetListEntryStableId: Hashable {
|
|||||||
case listFooter
|
case listFooter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct PresetIndex: Equatable {
|
||||||
|
let value: Int
|
||||||
|
|
||||||
|
static func ==(lhs: PresetIndex, rhs: PresetIndex) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||||
case screenHeader(String)
|
case screenHeader(String)
|
||||||
case suggestedListHeader(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 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 addItem(text: String, isEditing: Bool)
|
||||||
case listFooter(String)
|
case listFooter(String)
|
||||||
|
|
||||||
@ -85,7 +93,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
|||||||
case .listHeader:
|
case .listHeader:
|
||||||
return 100
|
return 100
|
||||||
case let .preset(preset):
|
case let .preset(preset):
|
||||||
return 101 + preset.index
|
return 101 + preset.index.value
|
||||||
case .addItem:
|
case .addItem:
|
||||||
return 1000
|
return 1000
|
||||||
case .listFooter:
|
case .listFooter:
|
||||||
@ -93,7 +101,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
|||||||
case .suggestedListHeader:
|
case .suggestedListHeader:
|
||||||
return 1002
|
return 1002
|
||||||
case let .suggestedPreset(suggestedPreset):
|
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
|
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] = []
|
var entries: [ChatListFilterPresetListEntry] = []
|
||||||
|
|
||||||
entries.append(.screenHeader("Create folders for different groups of chats and\nquickly switch between them."))
|
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 {
|
for (filter, _) in filters {
|
||||||
if filter.data == data {
|
if filter.data == suggestedFilter.data {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,8 +195,9 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
|
|||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.listHeader("FOLDERS"))
|
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 {
|
if filters.count < 10 {
|
||||||
entries.append(.addItem(text: "Create New Folder", isEditing: state.isEditing))
|
entries.append(.addItem(text: "Create New Folder", isEditing: state.isEditing))
|
||||||
@ -181,15 +206,20 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
|
|||||||
|
|
||||||
if !filteredSuggestedFilters.isEmpty {
|
if !filteredSuggestedFilters.isEmpty {
|
||||||
entries.append(.suggestedListHeader("RECOMMENDED FOLDERS"))
|
entries.append(.suggestedListHeader("RECOMMENDED FOLDERS"))
|
||||||
for (title, label, data) in filteredSuggestedFilters {
|
for filter in filteredSuggestedFilters {
|
||||||
entries.append(.suggestedPreset(index: entries.count, title: title, label: label, preset: data))
|
entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title, label: filter.description, preset: filter.data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries
|
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 initialState = ChatListFilterPresetListControllerState()
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
@ -208,14 +238,12 @@ public func chatListFilterPresetListController(context: AccountContext, updated:
|
|||||||
return settings
|
return settings
|
||||||
})
|
})
|
||||||
|> deliverOnMainQueue).start(next: { settings in
|
|> deliverOnMainQueue).start(next: { settings in
|
||||||
updated(settings.filters)
|
|
||||||
|
|
||||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
||||||
})
|
})
|
||||||
}, openPreset: { preset in
|
}, openPreset: { preset in
|
||||||
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: updated))
|
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: { _ in }))
|
||||||
}, addNew: {
|
}, addNew: {
|
||||||
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: updated))
|
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: { _ in }))
|
||||||
}, setItemWithRevealedOptions: { preset, fromPreset in
|
}, setItemWithRevealedOptions: { preset, fromPreset in
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
@ -233,22 +261,23 @@ public func chatListFilterPresetListController(context: AccountContext, updated:
|
|||||||
return settings
|
return settings
|
||||||
})
|
})
|
||||||
|> deliverOnMainQueue).start(next: { settings in
|
|> deliverOnMainQueue).start(next: { settings in
|
||||||
updated(settings.filters)
|
|
||||||
|
|
||||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:])
|
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
|
|> map { preferences -> [ChatListFilter] in
|
||||||
let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
|
let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||||
return filtersState.filters
|
return filtersState.filters
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in
|
|> 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
|
return filters.map { filter -> (ChatListFilter, Int) in
|
||||||
let count: Int
|
let count: Int
|
||||||
if let cachedValue = chatCountCache.with({ dict -> Int? in
|
if let cachedValue = chatCountCache.with({ dict -> Int? in
|
||||||
@ -265,36 +294,93 @@ public func chatListFilterPresetListController(context: AccountContext, updated:
|
|||||||
}
|
}
|
||||||
return (filter, count)
|
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)] = [
|
let filtersWithCounts = Promise<[(ChatListFilter, Int)]>()
|
||||||
("Unread", "All unread chats", ChatListFilterData(categories: .all, excludeMuted: false, excludeRead: true, excludeArchived: false, includePeers: [], excludePeers: [])),
|
filtersWithCounts.set(filtersWithCountsSignal)
|
||||||
("Personal", "Exclude groups and channels", ChatListFilterData(categories: ChatListFilterPeerCategories.all.subtracting([.groups, .channels]), excludeMuted: false, excludeRead: false, excludeArchived: false, includePeers: [], excludePeers: [])),
|
|
||||||
]
|
let updatedFilterOrder = Promise<[Int32]?>(nil)
|
||||||
|
|
||||||
|
let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings])
|
||||||
|
|
||||||
let signal = combineLatest(queue: .mainQueue(),
|
let signal = combineLatest(queue: .mainQueue(),
|
||||||
context.sharedContext.presentationData,
|
context.sharedContext.presentationData,
|
||||||
statePromise.get(),
|
statePromise.get(),
|
||||||
filtersWithCounts,
|
filtersWithCounts.get(),
|
||||||
preferences
|
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 filterSettings = preferences.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings ?? ChatListFilterSettings.default
|
||||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: {
|
let leftNavigationButton: ItemListNavigationButton?
|
||||||
dismissImpl?()
|
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
|
let rightNavigationButton: ItemListNavigationButton
|
||||||
if state.isEditing {
|
if state.isEditing {
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
||||||
updateState { state in
|
let _ = (updatedFilterOrder.get()
|
||||||
var state = state
|
|> take(1)
|
||||||
state.isEditing = false
|
|> deliverOnMainQueue).start(next: { [weak updatedFilterOrder] updatedFilterOrderValue in
|
||||||
return state
|
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 {
|
} else {
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
||||||
updateState { state in
|
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 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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
@ -314,9 +400,15 @@ public func chatListFilterPresetListController(context: AccountContext, updated:
|
|||||||
}
|
}
|
||||||
|
|
||||||
let controller = ItemListController(context: context, state: signal)
|
let controller = ItemListController(context: context, state: signal)
|
||||||
controller.navigationPresentation = .modal
|
switch mode {
|
||||||
controller.willDisappear = { _ in
|
case .default:
|
||||||
|
controller.navigationPresentation = .default
|
||||||
|
case .modal:
|
||||||
|
controller.navigationPresentation = .modal
|
||||||
|
}
|
||||||
|
controller.didDisappear = { _ in
|
||||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
||||||
|
dismissed?()
|
||||||
}
|
}
|
||||||
pushControllerImpl = { [weak controller] c in
|
pushControllerImpl = { [weak controller] c in
|
||||||
controller?.push(c)
|
controller?.push(c)
|
||||||
@ -347,36 +439,46 @@ public func chatListFilterPresetListController(context: AccountContext, updated:
|
|||||||
afterAll = true
|
afterAll = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { filtersState in
|
return combineLatest(
|
||||||
var filtersState = filtersState
|
updatedFilterOrder.get() |> take(1),
|
||||||
if let index = filtersState.filters.firstIndex(where: { $0.id == fromFilter.preset.id }) {
|
filtersWithCounts.get() |> take(1)
|
||||||
filtersState.filters.remove(at: index)
|
)
|
||||||
|
|> 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 {
|
if let referenceFilter = referenceFilter {
|
||||||
var inserted = false
|
var inserted = false
|
||||||
for i in 0 ..< filtersState.filters.count {
|
for i in 0 ..< filters.count {
|
||||||
if filtersState.filters[i].id == referenceFilter.id {
|
if filters[i].id == referenceFilter.id {
|
||||||
if fromIndex < toIndex {
|
if fromIndex < toIndex {
|
||||||
filtersState.filters.insert(fromFilter.preset, at: i + 1)
|
filters.insert(fromFilter.preset, at: i + 1)
|
||||||
} else {
|
} else {
|
||||||
filtersState.filters.insert(fromFilter.preset, at: i)
|
filters.insert(fromFilter.preset, at: i)
|
||||||
}
|
}
|
||||||
inserted = true
|
inserted = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !inserted {
|
if !inserted {
|
||||||
filtersState.filters.append(fromFilter.preset)
|
filters.append(fromFilter.preset)
|
||||||
}
|
}
|
||||||
} else if beforeAll {
|
} else if beforeAll {
|
||||||
filtersState.filters.insert(fromFilter.preset, at: 0)
|
filters.insert(fromFilter.preset, at: 0)
|
||||||
} else if afterAll {
|
} 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) {
|
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)
|
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)
|
self.shortTitleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - shortTitleSize.height) / 2.0)), size: shortTitleSize)
|
||||||
|
|
||||||
if let deleteButtonNode = self.deleteButtonNode {
|
if let deleteButtonNode = self.deleteButtonNode {
|
||||||
@ -609,10 +609,12 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
for filter in reorderedFilters {
|
for filter in reorderedFilters {
|
||||||
let itemNode: ItemNode
|
let itemNode: ItemNode
|
||||||
|
var itemNodeTransition = transition
|
||||||
var wasAdded = false
|
var wasAdded = false
|
||||||
if let current = self.itemNodes[filter.id] {
|
if let current = self.itemNodes[filter.id] {
|
||||||
itemNode = current
|
itemNode = current
|
||||||
} else {
|
} else {
|
||||||
|
itemNodeTransition = .immediate
|
||||||
wasAdded = true
|
wasAdded = true
|
||||||
itemNode = ItemNode(pressed: { [weak self] in
|
itemNode = ItemNode(pressed: { [weak self] in
|
||||||
self?.tabSelected?(filter.id)
|
self?.tabSelected?(filter.id)
|
||||||
@ -646,7 +648,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||||||
if !wasAdded && (itemNode.unreadCount != 0) != (unreadCount != 0) {
|
if !wasAdded && (itemNode.unreadCount != 0) != (unreadCount != 0) {
|
||||||
badgeAnimations[filter.id] = (unreadCount != 0) ? .in : .out
|
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] = []
|
var removeKeys: [ChatListFilterTabEntryId] = []
|
||||||
for (id, _) in self.itemNodes {
|
for (id, _) in self.itemNodes {
|
||||||
@ -672,10 +674,12 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let wasAdded = itemNode.supernode == nil
|
let wasAdded = itemNode.supernode == nil
|
||||||
|
var itemNodeTransition = transition
|
||||||
if wasAdded {
|
if wasAdded {
|
||||||
|
itemNodeTransition = .immediate
|
||||||
self.scrollNode.addSubnode(itemNode)
|
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 paneNodeSize = CGSize(width: paneNodeWidth, height: size.height)
|
||||||
let paneNodeShortSize = CGSize(width: paneNodeShortWidth, height: size.height)
|
let paneNodeShortSize = CGSize(width: paneNodeShortWidth, height: size.height)
|
||||||
tabSizes.append((filter.id, paneNodeSize, paneNodeShortSize, itemNode, wasAdded))
|
tabSizes.append((filter.id, paneNodeSize, paneNodeShortSize, itemNode, wasAdded))
|
||||||
@ -709,27 +713,32 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
for i in 0 ..< tabSizes.count {
|
for i in 0 ..< tabSizes.count {
|
||||||
let (itemId, paneNodeLongSize, paneNodeShortSize, paneNode, wasAdded) = tabSizes[i]
|
let (itemId, paneNodeLongSize, paneNodeShortSize, paneNode, wasAdded) = tabSizes[i]
|
||||||
|
var itemNodeTransition = transition
|
||||||
|
if wasAdded {
|
||||||
|
itemNodeTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
let useShortTitle = itemId == .all && useShortTitles
|
let useShortTitle = itemId == .all && useShortTitles
|
||||||
let paneNodeSize = useShortTitle ? paneNodeShortSize : paneNodeLongSize
|
let paneNodeSize = useShortTitle ? paneNodeShortSize : paneNodeLongSize
|
||||||
|
|
||||||
let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize)
|
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 {
|
if itemId == self.reorderingItem, let (initial, offset) = self.reorderingItemPosition {
|
||||||
transition.updateSublayerTransformScale(node: paneNode, scale: 1.2)
|
itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.2)
|
||||||
transition.updateAlpha(node: paneNode, alpha: 0.9)
|
itemNodeTransition.updateAlpha(node: paneNode, alpha: 0.9)
|
||||||
transition.updateFrameAdditive(node: paneNode, frame: CGRect(origin: CGPoint(x: initial + offset, y: paneFrame.minY), size: paneFrame.size))
|
itemNodeTransition.updateFrameAdditive(node: paneNode, frame: CGRect(origin: CGPoint(x: initial + offset, y: paneFrame.minY), size: paneFrame.size))
|
||||||
} else {
|
} else {
|
||||||
transition.updateSublayerTransformScale(node: paneNode, scale: 1.0)
|
itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.0)
|
||||||
transition.updateAlpha(node: paneNode, alpha: 1.0)
|
itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0)
|
||||||
if wasAdded {
|
if wasAdded {
|
||||||
paneNode.frame = paneFrame
|
paneNode.frame = paneFrame
|
||||||
paneNode.alpha = 0.0
|
paneNode.alpha = 0.0
|
||||||
transition.updateAlpha(node: paneNode, alpha: 1.0)
|
itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0)
|
||||||
} else {
|
} 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)
|
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0)
|
||||||
|
|
||||||
selectionFrames.append(paneFrame)
|
selectionFrames.append(paneFrame)
|
||||||
@ -774,14 +783,11 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
transition.updateAlpha(node: self.selectedLineNode, alpha: isReordering && selectedFilter == .all ? 0.5 : 1.0)
|
transition.updateAlpha(node: self.selectedLineNode, alpha: isReordering && selectedFilter == .all ? 0.5 : 1.0)
|
||||||
|
|
||||||
//if !transitionFraction.isZero {
|
if let previousSelectedFrame = self.previousSelectedFrame {
|
||||||
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 - previousScrollBounds.width, floor(previousSelectedFrame.midX - previousScrollBounds.width / 2.0)))
|
||||||
let previousContentOffsetX = max(0.0, min(previousContentWidth - self.scrollNode.bounds.width, floor(previousSelectedFrame.midX - self.scrollNode.bounds.width / 2.0)))
|
focusOnSelectedFilter = abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0
|
||||||
if abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0 {
|
}
|
||||||
focusOnSelectedFilter = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//}
|
|
||||||
if focusOnSelectedFilter && self.reorderingItem == nil {
|
if focusOnSelectedFilter && self.reorderingItem == nil {
|
||||||
let updatedBounds: CGRect
|
let updatedBounds: CGRect
|
||||||
if transitionFraction.isZero && selectedFilter == reorderedFilters.first?.id {
|
if transitionFraction.isZero && selectedFilter == reorderedFilters.first?.id {
|
||||||
@ -795,17 +801,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||||||
self.scrollNode.bounds = updatedBounds
|
self.scrollNode.bounds = updatedBounds
|
||||||
}
|
}
|
||||||
transition.animateHorizontalOffsetAdditive(node: self.scrollNode, offset: previousScrollBounds.minX - self.scrollNode.bounds.minX)
|
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.previousSelectedAbsFrame = selectedFrame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0)
|
||||||
self.previousSelectedFrame = selectedFrame
|
self.previousSelectedFrame = selectedFrame
|
||||||
} else {
|
} else {
|
||||||
|
@ -1474,7 +1474,7 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
var scrollToItem: ListViewScrollToItem?
|
var scrollToItem: ListViewScrollToItem?
|
||||||
switch self.visibleContentOffset() {
|
switch self.visibleContentOffset() {
|
||||||
case let .known(value) where abs(value) < navigationBarSearchContentHeight:
|
case let .known(value) where abs(value) < navigationBarSearchContentHeight - 1.0:
|
||||||
if isNavigationHidden {
|
if isNavigationHidden {
|
||||||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
|
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 options: ListViewDeleteAndInsertOptions = []
|
||||||
var maxAnimatedInsertionIndex = -1
|
var maxAnimatedInsertionIndex = -1
|
||||||
var stationaryItemRange: (Int, Int)?
|
|
||||||
var scrollToItem: ListViewScrollToItem?
|
var scrollToItem: ListViewScrollToItem?
|
||||||
|
|
||||||
switch reason {
|
switch reason {
|
||||||
@ -168,7 +167,17 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV
|
|||||||
|
|
||||||
var fromEmptyView = false
|
var fromEmptyView = false
|
||||||
if let fromView = fromView {
|
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(.AnimateInsertion)
|
||||||
options.remove(.AnimateAlpha)
|
options.remove(.AnimateAlpha)
|
||||||
fromEmptyView = true
|
fromEmptyView = true
|
||||||
@ -182,7 +191,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV
|
|||||||
adjustScrollToFirstItem = true
|
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()
|
subscriber.putCompletion()
|
||||||
|
|
||||||
return EmptyDisposable
|
return EmptyDisposable
|
||||||
|
@ -991,7 +991,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset, topItemFound {
|
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)
|
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,6 +433,12 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
|||||||
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||||
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0, beginWithCurrentState: true)
|
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
|
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()
|
completion()
|
||||||
})
|
})
|
||||||
return positionTransition
|
return positionTransition
|
||||||
|
@ -215,15 +215,36 @@ public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], isLess
|
|||||||
for item in rightList {
|
for item in rightList {
|
||||||
rightStableIds.append(getId(item))
|
rightStableIds.append(getId(item))
|
||||||
}
|
}
|
||||||
if Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds {
|
if Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds && !allUpdated {
|
||||||
/*var i = 0
|
var updatedItems: [(T, AnyHashable)] = []
|
||||||
var j = 0
|
for i in 0 ..< leftList.count {
|
||||||
while true {
|
|
||||||
if getId(leftList[i]) != getId(rightList[i]) {
|
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
|
var currentList = leftList
|
||||||
|
@ -391,6 +391,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
if let context = arguments.context {
|
if let context = arguments.context {
|
||||||
let _ = (context.account.postbox.transaction { transaction -> Void in
|
let _ = (context.account.postbox.transaction { transaction -> Void in
|
||||||
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedPollResults)
|
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedPollResults)
|
||||||
|
unmarkChatListFeaturedFiltersAsSeen(transaction: transaction)
|
||||||
}).start()
|
}).start()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1263,7 +1263,8 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
|||||||
let _ = (contextValue.get()
|
let _ = (contextValue.get()
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> take(1)).start(next: { context in
|
|> 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 contentSettings = 19
|
||||||
case chatListFilters = 20
|
case chatListFilters = 20
|
||||||
case peersNearby = 21
|
case peersNearby = 21
|
||||||
|
case chatListFiltersFeaturedState = 22
|
||||||
}
|
}
|
||||||
|
|
||||||
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
||||||
@ -330,6 +331,12 @@ public struct PreferencesKeys {
|
|||||||
key.setInt32(0, value: PreferencesKeyValues.peersNearby.rawValue)
|
key.setInt32(0, value: PreferencesKeyValues.peersNearby.rawValue)
|
||||||
return key
|
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 {
|
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(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(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(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>] = [
|
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
||||||
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
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(ChatListFiltersState.self, f: { ChatListFiltersState(decoder: $0) })
|
||||||
declareEncodable(PeersNearbyState.self, f: { PeersNearbyState(decoder: $0) })
|
declareEncodable(PeersNearbyState.self, f: { PeersNearbyState(decoder: $0) })
|
||||||
declareEncodable(TelegramMediaDice.self, f: { TelegramMediaDice(decoder: $0) })
|
declareEncodable(TelegramMediaDice.self, f: { TelegramMediaDice(decoder: $0) })
|
||||||
|
declareEncodable(ChatListFiltersFeaturedState.self, f: { ChatListFiltersFeaturedState(decoder: $0) })
|
||||||
|
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
@ -329,8 +329,11 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
|
func managedChatListFilters(postbox: Postbox, network: Network) -> Disposable {
|
||||||
return requestChatListFilters(postbox: postbox, network: network)
|
let disposables = DisposableSet()
|
||||||
|
disposables.add(updateChatListFeaturedFilters(postbox: postbox, network: network).start())
|
||||||
|
|
||||||
|
disposables.add((requestChatListFilters(postbox: postbox, network: network)
|
||||||
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
|
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
@ -343,7 +346,9 @@ func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Never,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
}
|
}).start())
|
||||||
|
|
||||||
|
return disposables
|
||||||
}
|
}
|
||||||
|
|
||||||
public func replaceRemoteChatListFilters(account: Account) -> Signal<Never, NoError> {
|
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.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)))
|
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))
|
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
|
eventIsPresses = event.type == .presses
|
||||||
}
|
}
|
||||||
if event.type == .touches || eventIsPresses {
|
if event.type == .touches || eventIsPresses {
|
||||||
self.requestDismiss()
|
//self.requestDismiss()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,6 +144,10 @@ public final class TooltipScreen: ViewController {
|
|||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
self.controllerNode.animateIn()
|
self.controllerNode.animateIn()
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5.0, execute: { [weak self] in
|
||||||
|
self?.dismiss()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user