Filter improvements and suggested filters

This commit is contained in:
Ali 2020-03-08 22:30:22 +04:00
parent ec69b23599
commit 88cc91a43c
21 changed files with 510 additions and 134 deletions

View File

@ -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

View File

@ -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,
)
)

View File

@ -20,6 +20,7 @@ EXPECTED_VARIABLES=(\
BUILD_NUMBER \
APP_VERSION \
BUNDLE_ID \
DEVELOPMENT_TEAM \
API_ID \
API_HASH \
APP_CENTER_ID \

View File

@ -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"

View File

@ -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 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()
})
})))

View File

@ -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

View File

@ -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 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 filtersWithCounts = Promise<[(ChatListFilter, Int)]>()
filtersWithCounts.set(filtersWithCountsSignal)
let updatedFilterOrder = Promise<[Int32]?>(nil)
let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings])
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 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: {
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: {
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)
switch mode {
case .default:
controller.navigationPresentation = .default
case .modal:
controller.navigationPresentation = .modal
controller.willDisappear = { _ in
}
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
}
})

View File

@ -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 {

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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()
}
})

View File

@ -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)
})
})

View File

@ -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 {

View File

@ -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 : [] },

View File

@ -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
}()

View File

@ -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
}
}

View File

@ -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() {