Chat folder improvements

This commit is contained in:
Ali 2020-03-10 17:54:16 +05:30
parent ee2a6ca70f
commit 566816d373
20 changed files with 658 additions and 252 deletions

View File

@ -498,7 +498,9 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__);
ASAssertLocked(__instanceLock__);
UIView *view = nil;
bool initializedWithCustomView = false;
if (_viewBlock) {
initializedWithCustomView = true;
view = _viewBlock();
ASDisplayNodeAssertNotNil(view, @"View block returned nil");
ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view");
@ -518,6 +520,19 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__);
_flags.canCallSetNeedsDisplayOfLayer = NO;
}
if (initializedWithCustomView) {
static dispatch_once_t onceToken;
static IMP defaultMethod = NULL;
dispatch_once(&onceToken, ^{
defaultMethod = [[UIView class] instanceMethodForSelector:@selector(drawRect:)];
});
if ([[view class] instanceMethodForSelector:@selector(drawRect:)] != defaultMethod) {
} else {
_flags.canClearContentsOfLayer = NO;
_flags.canCallSetNeedsDisplayOfLayer = NO;
}
}
// UIActivityIndicator
if ([_viewClass isSubclassOfClass:[UIActivityIndicatorView class]]
|| [_viewClass isSubclassOfClass:[UIVisualEffectView class]]) {

View File

@ -468,10 +468,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|> deliverOnMainQueue).start(next: { [weak self] filters in
guard let strongSelf = self else {
return
@ -514,10 +511,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|> deliverOnMainQueue).start(next: { [weak self] filters in
guard let strongSelf = self else {
return
@ -531,10 +525,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|> deliverOnMainQueue).start(next: { presetList in
guard let strongSelf = self else {
return
@ -563,10 +554,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|> deliverOnMainQueue).start(next: { presetList in
guard let strongSelf = self else {
return
@ -1150,17 +1138,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
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
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|> deliverOnMainQueue).start(next: { filters in
guard let strongSelf = self else {
return
}
let hasFilters = !filters.isEmpty
if let _ = strongSelf.validLayout, let parentController = strongSelf.parent as? TabBarController, let sourceFrame = parentController.frameForControllerTab(controller: strongSelf) {
let absoluteFrame = sourceFrame
//TODO:localize
@ -1295,29 +1278,26 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
@objc private func reorderingDonePressed() {
if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds {
let _ = (updateChatListFilterSettingsInteractively(postbox: self.context.account.postbox, { state in
var state = state
let _ = (updateChatListFiltersInteractively(postbox: self.context.account.postbox, { stateFilters in
var updatedFilters: [ChatListFilter] = []
for id in reorderedFilterIds {
if let index = state.filters.firstIndex(where: { $0.id == id }) {
updatedFilters.append(state.filters[index])
if let index = stateFilters.firstIndex(where: { $0.id == id }) {
updatedFilters.append(stateFilters[index])
}
}
updatedFilters.append(contentsOf: state.filters.compactMap { filter -> ChatListFilter? in
updatedFilters.append(contentsOf: stateFilters.compactMap { filter -> ChatListFilter? in
if !updatedFilters.contains(where: { $0.id == filter.id }) {
return filter
} else {
return nil
}
})
state.filters = updatedFilters
return state
return updatedFilters
})
|> deliverOnMainQueue).start(completed: { [weak self] in
guard let strongSelf = self else {
return
}
let _ = replaceRemoteChatListFilters(account: strongSelf.context.account).start()
strongSelf.reloadFilters(firstUpdate: {
guard let strongSelf = self else {
return
@ -1451,12 +1431,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
}
let _ = updateChatListFilterSettingsInteractively(postbox: strongSelf.context.account.postbox, { settings in
var settings = settings
settings.filters = settings.filters.filter({ $0.id != id })
return settings
let _ = updateChatListFiltersInteractively(postbox: strongSelf.context.account.postbox, { filters in
return filters.filter({ $0.id != id })
}).start()
let _ = replaceRemoteChatListFilters(account: strongSelf.context.account).start()
}
if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id == id {
@ -2332,10 +2309,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
let _ = (combineLatest(queue: .mainQueue(),
self.context.account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
},
currentChatListFilters(postbox: self.context.account.postbox),
chatListFilterItems(context: self.context)
|> take(1)
)
@ -2350,7 +2324,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presetList.isEmpty ? "Add Folder" : "Edit Folders", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
return generateTintedImage(image: UIImage(bundleImageName: presetList.isEmpty ? "Chat/Context Menu/Add" : "Chat/Context Menu/ItemList"), color: theme.contextMenu.primaryColor)
}, action: { c, f in
c.dismiss(completion: {
guard let strongSelf = self else {

View File

@ -539,20 +539,10 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.applyItemNodeAsCurrent(id: .all, itemNode: itemNode)
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
guard let strongSelf = self, let index = strongSelf.availableFilters.firstIndex(where: { $0.id == strongSelf.selectedId }) else {
guard let strongSelf = self, strongSelf.availableFilters.count > 1 else {
return []
}
var directions: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter]
if strongSelf.availableFilters.count > 1 {
if index == 0 {
directions.remove(.rightCenter)
}
if index == strongSelf.availableFilters.count - 1 {
directions.remove(.leftCenter)
}
} else {
directions = []
}
let directions: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter]
return directions
}, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0))
panRecognizer.delegate = self
@ -601,11 +591,21 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, isReorderingFilters, isEditing) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) {
let translation = recognizer.translation(in: self.view)
var transitionFraction = translation.x / layout.size.width
if selectedIndex <= 0 {
transitionFraction = min(0.0, transitionFraction)
func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat {
let bandedOffset = offset - bandingStart
let range: CGFloat = 600.0
let coefficient: CGFloat = 0.4
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
}
if selectedIndex >= self.availableFilters.count - 1 {
transitionFraction = max(0.0, transitionFraction)
if selectedIndex <= 0 && translation.x > 0.0 {
let overscroll = translation.x
transitionFraction = rubberBandingOffset(offset: overscroll, bandingStart: 0.0) / layout.size.width
}
if selectedIndex >= self.availableFilters.count - 1 && translation.x < 0.0 {
let overscroll = -translation.x
transitionFraction = -rubberBandingOffset(offset: overscroll, bandingStart: 0.0) / layout.size.width
}
self.transitionFraction = transitionFraction + self.transitionFractionOffset
if let currentItemNode = self.currentItemNodeValue {

View File

@ -574,21 +574,19 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
}
if applyAutomatically {
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
for i in 0 ..< settings.filters.count {
if settings.filters[i].id == filter.id {
settings.filters[i].data.categories = categories
settings.filters[i].data.includePeers = includePeers
settings.filters[i].data.excludePeers = settings.filters[i].data.excludePeers.filter { !settings.filters[i].data.includePeers.contains($0) }
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
var filters = filters
for i in 0 ..< filters.count {
if filters[i].id == filter.id {
filters[i].data.categories = categories
filters[i].data.includePeers = includePeers
filters[i].data.excludePeers = filters[i].data.excludePeers.filter { !filters[i].data.includePeers.contains($0) }
}
}
return settings
return filters
})
|> deliverOnMainQueue).start(next: { settings in
|> deliverOnMainQueue).start(next: { _ in
controller?.dismiss()
let _ = replaceRemoteChatListFilters(account: context.account).start()
})
} else {
var filter = filter
@ -653,23 +651,21 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
excludePeers.sort()
if applyAutomatically {
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
for i in 0 ..< settings.filters.count {
if settings.filters[i].id == filter.id {
settings.filters[i].data.excludeMuted = additionalCategoryIds.contains(AdditionalExcludeCategoryId.muted.rawValue)
settings.filters[i].data.excludeRead = additionalCategoryIds.contains(AdditionalExcludeCategoryId.read.rawValue)
settings.filters[i].data.excludeArchived = additionalCategoryIds.contains(AdditionalExcludeCategoryId.archived.rawValue)
settings.filters[i].data.excludePeers = excludePeers
settings.filters[i].data.includePeers = settings.filters[i].data.includePeers.filter { !settings.filters[i].data.excludePeers.contains($0) }
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
var filters = filters
for i in 0 ..< filters.count {
if filters[i].id == filter.id {
filters[i].data.excludeMuted = additionalCategoryIds.contains(AdditionalExcludeCategoryId.muted.rawValue)
filters[i].data.excludeRead = additionalCategoryIds.contains(AdditionalExcludeCategoryId.read.rawValue)
filters[i].data.excludeArchived = additionalCategoryIds.contains(AdditionalExcludeCategoryId.archived.rawValue)
filters[i].data.excludePeers = excludePeers
filters[i].data.includePeers = filters[i].data.includePeers.filter { !filters[i].data.excludePeers.contains($0) }
}
}
return settings
return filters
})
|> deliverOnMainQueue).start(next: { settings in
|> deliverOnMainQueue).start(next: { _ in
controller?.dismiss()
let _ = replaceRemoteChatListFilters(account: context.account).start()
})
} else {
var filter = filter
@ -921,39 +917,37 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
var applyImpl: (() -> Void)? = {
let state = stateValue.with { $0 }
let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers))
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
var preset = preset
if currentPreset == nil {
preset.id = max(2, settings.filters.map({ $0.id + 1 }).max() ?? 2)
preset.id = max(2, filters.map({ $0.id + 1 }).max() ?? 2)
}
var settings = settings
var filters = filters
if let _ = currentPreset {
var found = false
for i in 0 ..< settings.filters.count {
if settings.filters[i].id == preset.id {
settings.filters[i] = preset
for i in 0 ..< filters.count {
if filters[i].id == preset.id {
filters[i] = preset
found = true
}
}
if !found {
settings.filters = settings.filters.filter { listFilter in
filters = filters.filter { listFilter in
if listFilter.title == preset.title && listFilter.data == preset.data {
return false
}
return true
}
settings.filters.append(preset)
filters.append(preset)
}
} else {
settings.filters.append(preset)
filters.append(preset)
}
return settings
return filters
})
|> deliverOnMainQueue).start(next: { settings in
updated(settings.filters)
|> deliverOnMainQueue).start(next: { filters in
updated(filters)
dismissImpl?()
let _ = replaceRemoteChatListFilters(account: context.account).start()
})
}

View File

@ -248,13 +248,13 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
let arguments = ChatListFilterPresetListControllerArguments(context: context,
addSuggestedPresed: { title, data in
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.filters.insert(ChatListFilter(id: max(2, settings.filters.map({ $0.id + 1 }).max() ?? 2), title: title, data: data), at: 0)
return settings
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
var filters = filters
let id = generateNewChatListFilterId(filters: filters)
filters.insert(ChatListFilter(id: id, title: title, data: data), at: 0)
return filters
})
|> deliverOnMainQueue).start(next: { settings in
let _ = replaceRemoteChatListFilters(account: context.account).start()
|> deliverOnMainQueue).start(next: { _ in
})
}, openPreset: { preset in
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: { _ in }))
@ -269,25 +269,20 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
return state
}
}, removePreset: { id in
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
if let index = settings.filters.firstIndex(where: { $0.id == id }) {
settings.filters.remove(at: index)
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
var filters = filters
if let index = filters.firstIndex(where: { $0.id == id }) {
filters.remove(at: index)
}
return settings
return filters
})
|> deliverOnMainQueue).start(next: { settings in
let _ = replaceRemoteChatListFilters(account: context.account).start()
|> deliverOnMainQueue).start(next: { _ in
})
})
let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:])
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
}
let filtersWithCountsSignal = updatedChatListFilters(postbox: context.account.postbox)
|> distinctUntilChanged
|> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in
return .single(filters.map { filter -> (ChatListFilter, Int) in
@ -355,24 +350,20 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|> take(1)
|> deliverOnMainQueue).start(next: { [weak updatedFilterOrder] updatedFilterOrderValue in
if let updatedFilterOrderValue = updatedFilterOrderValue {
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { filtersState in
var filtersState = filtersState
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
var updatedFilters: [ChatListFilter] = []
for id in updatedFilterOrderValue {
if let index = filtersState.filters.firstIndex(where: { $0.id == id }) {
updatedFilters.append(filtersState.filters[index])
if let index = filters.firstIndex(where: { $0.id == id }) {
updatedFilters.append(filters[index])
}
}
for filter in filtersState.filters {
for filter in filters {
if !updatedFilters.contains(where: { $0.id == filter.id }) {
updatedFilters.append(filter)
}
}
filtersState.filters = updatedFilters
return filtersState
return updatedFilters
})
|> deliverOnMainQueue).start(next: { _ in
filtersWithCounts.set(filtersWithCountsSignal)
@ -425,7 +416,6 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
controller.navigationPresentation = .modal
}
controller.didDisappear = { _ in
let _ = replaceRemoteChatListFilters(account: context.account).start()
dismissed?()
}
pushControllerImpl = { [weak controller] c in

View File

@ -697,10 +697,10 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
let minSpacing: CGFloat = 26.0
let sideInset: CGFloat = 16.0
var leftOffset: CGFloat = sideInset
let resolvedSideInset: CGFloat = 16.0 + sideInset
var leftOffset: CGFloat = resolvedSideInset
var longTitlesWidth: CGFloat = sideInset
var longTitlesWidth: CGFloat = resolvedSideInset
for i in 0 ..< tabSizes.count {
let (itemId, paneNodeSize, paneNodeShortSize, paneNode, wasAdded) = tabSizes[i]
longTitlesWidth += paneNodeSize.width
@ -708,7 +708,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
longTitlesWidth += minSpacing
}
}
longTitlesWidth += sideInset
longTitlesWidth += resolvedSideInset
let useShortTitles = longTitlesWidth > size.width
for i in 0 ..< tabSizes.count {
@ -746,7 +746,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
leftOffset += paneNodeSize.width + minSpacing
}
leftOffset -= minSpacing
leftOffset += sideInset
leftOffset += resolvedSideInset
self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height)

View File

@ -1665,7 +1665,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let leftInset: CGFloat = params.leftInset + avatarLeftInset
let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + 8.0), size: CGSize(width: params.width - leftInset - params.rightInset - 10.0 - 1.0 - editingOffset, height: self.bounds.size.height - 12.0 - 9.0))
let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + 8.0), size: CGSize(width: params.width - leftInset - params.rightInset - 10.0 - editingOffset, height: self.bounds.size.height - 12.0 - 9.0))
let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + offset, dy: 0.0)

View File

@ -1243,15 +1243,10 @@ public final class ChatListNode: ListView {
}
private func resetFilter() {
if let chatListFilter = chatListFilter {
let preferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.chatListFilters]))
self.updatedFilterDisposable.set((context.account.postbox.combinedView(keys: [preferencesKey])
|> map { view -> ChatListFilter? in
guard let preferencesView = view.views[preferencesKey] as? PreferencesView else {
return nil
}
let filersState = preferencesView.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
for filter in filersState.filters {
if let chatListFilter = self.chatListFilter {
self.updatedFilterDisposable.set((updatedChatListFilters(postbox: self.context.account.postbox)
|> map { filters -> ChatListFilter? in
for filter in filters {
if filter.id == chatListFilter.id {
return filter
}

View File

@ -11,15 +11,7 @@ import TelegramUIPreferences
import TelegramCore
func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilter, Int)]), NoError> {
let preferencesKey: PostboxViewKey = .preferences(keys: [PreferencesKeys.chatListFilters])
return context.account.postbox.combinedView(keys: [preferencesKey])
|> map { combinedView -> [ChatListFilter] in
if let filtersState = (combinedView.views[preferencesKey] as? PreferencesView)?.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState {
return filtersState.filters
} else {
return []
}
}
return updatedChatListFilters(postbox: context.account.postbox)
|> distinctUntilChanged
|> mapToSignal { filters -> Signal<(Int, [(ChatListFilter, Int)]), NoError> in
var unreadCountItems: [UnreadMessageCountsItem] = []

View File

@ -1492,6 +1492,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Hide
self.lockOrientation = true
}
required init(coder aDecoder: NSCoder) {

View File

@ -873,8 +873,14 @@ open class NavigationBar: ASDisplayNode {
let initialX: CGFloat = backButtonInset
let finalX: CGFloat = floor((size.width - backButtonSize.width) / 2.0) - size.width
self.backButtonNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize)
self.backButtonNode.alpha = (1.0 - progress) * (1.0 - progress)
let backButtonFrame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize)
if self.backButtonNode.frame != backButtonFrame {
self.backButtonNode.frame = backButtonFrame
}
let backButtonAlpha = self.backButtonNode.alpha
if self.backButtonNode.alpha != backButtonAlpha {
self.backButtonNode.alpha = backButtonAlpha
}
if let transitionTitleNode = self.transitionTitleNode {
let transitionTitleSize = transitionTitleNode.measure(CGSize(width: size.width, height: nominalHeight))

View File

@ -138,6 +138,7 @@ public struct OperationLogTags {
public static let SynchronizeRecentlyUsedStickers = PeerOperationLogTag(value: 17)
public static let SynchronizeAppLogEvents = PeerOperationLogTag(value: 18)
public static let SynchronizeEmojiKeywords = PeerOperationLogTag(value: 19)
public static let SynchronizeChatListFilters = PeerOperationLogTag(value: 20)
}
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {

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))
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network).start())
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },

View File

@ -97,6 +97,9 @@ enum AccountStateMutationOperation {
case UpdatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool)
case UpdatePeersNearby([PeerNearby])
case UpdateTheme(TelegramTheme)
case SyncChatListFilters
case UpdateChatListFilterOrder(order: [Int32])
case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?)
}
struct AccountMutableState {
@ -408,9 +411,21 @@ struct AccountMutableState {
self.addOperation(.UpdateCall(call))
}
mutating func addSyncChatListFilters() {
self.addOperation(.SyncChatListFilters)
}
mutating func addUpdateChatListFilterOrder(order: [Int32]) {
self.addOperation(.UpdateChatListFilterOrder(order: order))
}
mutating func addUpdateChatListFilter(id: Int32, filter: Api.DialogFilter?) {
self.addOperation(.UpdateChatListFilter(id: id, filter: filter))
}
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter:
break
case let .AddMessages(messages, location):
for message in messages {

View File

@ -156,6 +156,7 @@ private var declaredEncodables: Void = {
declareEncodable(PeersNearbyState.self, f: { PeersNearbyState(decoder: $0) })
declareEncodable(TelegramMediaDice.self, f: { TelegramMediaDice(decoder: $0) })
declareEncodable(ChatListFiltersFeaturedState.self, f: { ChatListFiltersFeaturedState(decoder: $0) })
declareEncodable(SynchronizeChatListFiltersOperation.self, f: { SynchronizeChatListFiltersOperation(decoder: $0) })
return
}()

View File

@ -1320,6 +1320,12 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
}
case let .updateMessageID(id, randomId):
updatedState.updatedOutgoingUniqueMessageIds[randomId] = id
case .updateDialogFilters:
updatedState.addSyncChatListFilters()
case let .updateDialogFilterOrder(order):
updatedState.addUpdateChatListFilterOrder(order: order)
case let .updateDialogFilter(_, id, filter):
updatedState.addUpdateChatListFilter(id: id, filter: filter)
default:
break
}
@ -2042,7 +2048,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -2126,6 +2132,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
var updatedThemes: [Int64: TelegramTheme] = [:]
var delayNotificatonsUntil: Int32?
var peerActivityTimestamps: [PeerId: Int32] = [:]
var syncChatListFilters = false
var holesFromPreviousStateMessageIds: [MessageId] = []
@ -2683,6 +2690,46 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
updatedPeersNearby = peersNearby
case let .UpdateTheme(theme):
updatedThemes[theme.id] = theme
case .SyncChatListFilters:
syncChatListFilters = true
case let .UpdateChatListFilterOrder(order):
if !syncChatListFilters {
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
if Set(state.filters.map { $0.id }) == Set(order) {
var updatedFilters: [ChatListFilter] = []
for id in order {
if let filter = state.filters.first(where: { $0.id == id }) {
updatedFilters.append(filter)
} else {
assertionFailure()
}
}
state.filters = updatedFilters
state.remoteFilters = state.filters
} else {
syncChatListFilters = true
}
return state
})
}
case let .UpdateChatListFilter(id, filter):
if !syncChatListFilters {
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
if let index = state.filters.firstIndex(where: { $0.id == id }) {
if let filter = filter {
state.filters[index] = ChatListFilter(apiFilter: filter)
} else {
state.filters.remove(at: index)
}
state.remoteFilters = state.filters
} else {
syncChatListFilters = true
}
return state
})
}
}
}
@ -3003,5 +3050,9 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
}
}
if syncChatListFilters {
requestChatListFiltersSync(transaction: transaction)
}
return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil)
}

View File

@ -226,8 +226,8 @@ public enum RequestUpdateChatListFilterError {
case generic
}
public func requestUpdateChatListFilter(account: Account, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
return account.postbox.transaction { transaction -> Api.DialogFilter? in
public func requestUpdateChatListFilter(postbox: Postbox, network: Network, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
return postbox.transaction { transaction -> Api.DialogFilter? in
return filter?.apiFilter(transaction: transaction)
}
|> castError(RequestUpdateChatListFilterError.self)
@ -236,7 +236,7 @@ public func requestUpdateChatListFilter(account: Account, id: Int32, filter: Cha
if inputFilter != nil {
flags |= 1 << 0
}
return account.network.request(Api.functions.messages.updateDialogFilter(flags: flags, id: id, filter: inputFilter))
return network.request(Api.functions.messages.updateDialogFilter(flags: flags, id: id, filter: inputFilter))
|> mapError { _ -> RequestUpdateChatListFilterError in
return .generic
}
@ -324,118 +324,143 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|> castError(RequestChatListFiltersError.self)
|> mapToSignal { filtersAndMissingPeers -> Signal<[ChatListFilter], RequestChatListFiltersError> in
let (filters, missingPeers) = filtersAndMissingPeers
return .single(filters)
}
}
}
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()
}
|> mapToSignal { filters -> Signal<Never, NoError> in
return postbox.transaction { transaction in
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
var settings = entry as? ChatListFiltersState ?? ChatListFiltersState.default
settings.filters = filters
return settings
})
}
|> ignoreValues
}).start())
return disposables
}
public func replaceRemoteChatListFilters(account: Account) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
|> mapToSignal { filters -> Signal<Never, NoError> in
return requestChatListFilters(postbox: account.postbox, network: account.network)
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
return .complete()
}
|> mapToSignal { remoteFilters -> Signal<Never, NoError> in
var deleteSignals: [Signal<Never, NoError>] = []
for filter in remoteFilters {
if !filters.contains(where: { $0.id == filter.id }) {
deleteSignals.append(requestUpdateChatListFilter(account: account, id: filter.id, filter: nil)
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
|> ignoreValues)
var missingUsers: [Api.InputUser] = []
var missingChannels: [Api.InputChannel] = []
var missingGroups: [Int32] = []
for peer in missingPeers {
switch peer {
case let .inputPeerUser(userId, accessHash):
missingUsers.append(.inputUser(userId: userId, accessHash: accessHash))
case .inputPeerSelf:
missingUsers.append(.inputUserSelf)
case let .inputPeerChannel(channelId, accessHash):
missingChannels.append(.inputChannel(channelId: channelId, accessHash: accessHash))
case let .inputPeerChat(id):
missingGroups.append(id)
case .inputPeerEmpty:
break
}
}
let addFilters = account.postbox.transaction { transaction -> [(Int32, ChatListFilter)] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters.map { filter -> (Int32, ChatListFilter) in
return (filter.id, filter)
let resolveMissingUsers: Signal<Never, NoError>
if !missingUsers.isEmpty {
resolveMissingUsers = network.request(Api.functions.users.getUsers(id: missingUsers))
|> `catch` { _ -> Signal<[Api.User], NoError> in
return .single([])
}
}
|> mapToSignal { filters -> Signal<Never, NoError> in
var signals: [Signal<Never, NoError>] = []
for (id, filter) in filters {
if !remoteFilters.contains(filter) {
signals.append(requestUpdateChatListFilter(account: account, id: id, filter: filter)
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
|> mapToSignal { users -> Signal<Never, NoError> in
return postbox.transaction { transaction -> Void in
var peers: [Peer] = []
for user in users {
peers.append(TelegramUser(user: user))
}
|> ignoreValues)
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
return updated
})
}
}
return combineLatest(signals)
|> ignoreValues
}
let reorderFilters: Signal<Never, NoError>
if remoteFilters.map({ $0.id }) != filters.map({ $0.id }) {
reorderFilters = account.network.request(Api.functions.messages.updateDialogFiltersOrder(order: filters.map { $0.id }))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
|> ignoreValues
}
} else {
reorderFilters = .complete()
resolveMissingUsers = .complete()
}
return combineLatest(
deleteSignals
)
|> ignoreValues
|> then(
addFilters
let resolveMissingChannels: Signal<Never, NoError>
if !missingChannels.isEmpty {
resolveMissingChannels = network.request(Api.functions.channels.getChannels(id: missingChannels))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.Chats?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
return postbox.transaction { transaction -> Void in
if let result = result {
var peers: [Peer] = []
switch result {
case .chats(let chats), .chatsSlice(_, let chats):
for chat in chats {
if let peer = parseTelegramGroupOrChannel(chat: chat) {
peers.append(peer)
}
}
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
return updated
})
}
}
|> ignoreValues
}
} else {
resolveMissingChannels = .complete()
}
let resolveMissingGroups: Signal<Never, NoError>
if !missingGroups.isEmpty {
resolveMissingGroups = network.request(Api.functions.messages.getChats(id: missingGroups))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.Chats?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
return postbox.transaction { transaction -> Void in
if let result = result {
var peers: [Peer] = []
switch result {
case .chats(let chats), .chatsSlice(_, let chats):
for chat in chats {
if let peer = parseTelegramGroupOrChannel(chat: chat) {
peers.append(peer)
}
}
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
return updated
})
}
}
|> ignoreValues
}
} else {
resolveMissingGroups = .complete()
}
return (
resolveMissingUsers
)
|> then(
reorderFilters
resolveMissingChannels
)
|> then(
resolveMissingGroups
)
|> castError(RequestChatListFiltersError.self)
|> mapToSignal { _ -> Signal<[ChatListFilter], RequestChatListFiltersError> in
}
|> then(
.single(filters)
)
}
}
}
public struct ChatListFiltersState: PreferencesEntry, Equatable {
public var filters: [ChatListFilter]
public var remoteFilters: [ChatListFilter]?
struct ChatListFiltersState: PreferencesEntry, Equatable {
var filters: [ChatListFilter]
var remoteFilters: [ChatListFilter]?
public static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil)
static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil)
public init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?) {
fileprivate init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?) {
self.filters = filters
self.remoteFilters = remoteFilters
}
public init(decoder: PostboxDecoder) {
init(decoder: PostboxDecoder) {
self.filters = decoder.decodeObjectArrayWithDecoderForKey("filters")
self.remoteFilters = decoder.decodeOptionalObjectArrayWithDecoderForKey("remoteFilters")
}
public func encode(_ encoder: PostboxEncoder) {
func encode(_ encoder: PostboxEncoder) {
encoder.encodeObjectArray(self.filters, forKey: "filters")
if let remoteFilters = self.remoteFilters {
encoder.encodeObjectArray(remoteFilters, forKey: "remoteFilters")
@ -444,7 +469,7 @@ public struct ChatListFiltersState: PreferencesEntry, Equatable {
}
}
public func isEqual(to: PreferencesEntry) -> Bool {
func isEqual(to: PreferencesEntry) -> Bool {
if let to = to as? ChatListFiltersState, self == to {
return true
} else {
@ -453,19 +478,63 @@ public struct ChatListFiltersState: PreferencesEntry, Equatable {
}
}
public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @escaping (ChatListFiltersState) -> ChatListFiltersState) -> Signal<ChatListFiltersState, NoError> {
return postbox.transaction { transaction -> ChatListFiltersState in
var result: ChatListFiltersState?
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
let settings = entry as? ChatListFiltersState ?? ChatListFiltersState.default
let updated = f(settings)
result = updated
return updated
})
return result ?? .default
public func generateNewChatListFilterId(filters: [ChatListFilter]) -> Int32 {
while true {
let id = Int32(2 + arc4random_uniform(255 - 2))
if !filters.contains(where: { $0.id == id }) {
return id
}
}
}
public func updateChatListFiltersInteractively(postbox: Postbox, _ f: @escaping ([ChatListFilter]) -> [ChatListFilter]) -> Signal<[ChatListFilter], NoError> {
return postbox.transaction { transaction -> [ChatListFilter] in
var updated: [ChatListFilter] = []
var hasUpdates = false
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
var state = entry as? ChatListFiltersState ?? ChatListFiltersState.default
let updatedFilters = f(state.filters)
if updatedFilters != state.filters {
state.filters = updatedFilters
hasUpdates = true
}
updated = updatedFilters
return state
})
if hasUpdates {
requestChatListFiltersSync(transaction: transaction)
}
return updated
}
}
public func updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> {
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|> map { preferences -> [ChatListFilter] in
let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
return filtersState.filters
}
|> distinctUntilChanged
}
public func currentChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> {
return postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
}
func updateChatListFiltersState(transaction: Transaction, _ f: (ChatListFiltersState) -> ChatListFiltersState) -> ChatListFiltersState {
var result: ChatListFiltersState?
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
let settings = entry as? ChatListFiltersState ?? ChatListFiltersState.default
let updated = f(settings)
result = updated
return updated
})
return result ?? .default
}
public struct ChatListFeaturedFilter: PostboxCoding, Equatable {
public var title: String
public var description: String
@ -579,3 +648,293 @@ public func updateChatListFeaturedFilters(postbox: Postbox, network: Network) ->
|> ignoreValues
}
}
private enum SynchronizeChatListFiltersOperationContentType: Int32 {
case add
case remove
case sync
}
private enum SynchronizeChatListFiltersOperationContent: PostboxCoding {
case sync
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("r", orElse: 0) {
case SynchronizeChatListFiltersOperationContentType.sync.rawValue:
self = .sync
default:
assertionFailure()
self = .sync
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case .sync:
encoder.encodeInt32(SynchronizeChatListFiltersOperationContentType.sync.rawValue, forKey: "r")
}
}
}
final class SynchronizeChatListFiltersOperation: PostboxCoding {
fileprivate let content: SynchronizeChatListFiltersOperationContent
fileprivate init(content: SynchronizeChatListFiltersOperationContent) {
self.content = content
}
init(decoder: PostboxDecoder) {
self.content = decoder.decodeObjectForKey("c", decoder: { SynchronizeChatListFiltersOperationContent(decoder: $0) }) as! SynchronizeChatListFiltersOperationContent
}
func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.content, forKey: "c")
}
}
private final class ManagedSynchronizeChatListFiltersOperationsHelper {
var operationDisposables: [Int32: Disposable] = [:]
func update(_ entries: [PeerMergedOperationLogEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) {
var disposeOperations: [Disposable] = []
var beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)] = []
var hasRunningOperationForPeerId = Set<PeerId>()
var validMergedIndices = Set<Int32>()
for entry in entries {
if !hasRunningOperationForPeerId.contains(entry.peerId) {
hasRunningOperationForPeerId.insert(entry.peerId)
validMergedIndices.insert(entry.mergedIndex)
if self.operationDisposables[entry.mergedIndex] == nil {
let disposable = MetaDisposable()
beginOperations.append((entry, disposable))
self.operationDisposables[entry.mergedIndex] = disposable
}
}
}
var removeMergedIndices: [Int32] = []
for (mergedIndex, disposable) in self.operationDisposables {
if !validMergedIndices.contains(mergedIndex) {
removeMergedIndices.append(mergedIndex)
disposeOperations.append(disposable)
}
}
for mergedIndex in removeMergedIndices {
self.operationDisposables.removeValue(forKey: mergedIndex)
}
return (disposeOperations, beginOperations)
}
func reset() -> [Disposable] {
let disposables = Array(self.operationDisposables.values)
self.operationDisposables.removeAll()
return disposables
}
}
private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal<Never, NoError>) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Signal<Never, NoError> in
var result: PeerMergedOperationLogEntry?
transaction.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeChatListFiltersOperation {
result = entry.mergedEntry!
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
} else {
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
}
})
return f(transaction, result)
}
|> switchToLatest
}
func requestChatListFiltersSync(transaction: Transaction) {
let tag: PeerOperationLogTag = OperationLogTags.SynchronizeChatListFilters
let peerId = PeerId(namespace: 0, id: 0)
var topOperation: (SynchronizeChatListFiltersOperation, Int32)?
transaction.operationLogEnumerateEntries(peerId: peerId, tag: tag, { entry in
if let operation = entry.contents as? SynchronizeChatListFiltersOperation {
topOperation = (operation, entry.tagLocalIndex)
}
return false
})
if let (topOperation, topLocalIndex) = topOperation, case .sync = topOperation.content {
let _ = transaction.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: topLocalIndex)
}
transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeChatListFiltersOperation(content: .sync))
}
func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
return Signal { _ in
let updateFeaturedDisposable = updateChatListFeaturedFilters(postbox: postbox, network: network).start()
let _ = postbox.transaction({ transaction in
requestChatListFiltersSync(transaction: transaction)
}).start()
let tag: PeerOperationLogTag = OperationLogTags.SynchronizeChatListFilters
let helper = Atomic<ManagedSynchronizeChatListFiltersOperationsHelper>(value: ManagedSynchronizeChatListFiltersOperationsHelper())
let disposable = postbox.mergedOperationLogView(tag: tag, limit: 10).start(next: { view in
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in
return helper.update(view.entries)
}
for disposable in disposeOperations {
disposable.dispose()
}
for (entry, disposable) in beginOperations {
let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal<Never, NoError> in
if let entry = entry {
if let operation = entry.contents as? SynchronizeChatListFiltersOperation {
return synchronizeChatListFilters(transaction: transaction, postbox: postbox, network: network, operation: operation)
} else {
assertionFailure()
}
}
return .complete()
})
|> then(
postbox.transaction { transaction -> Void in
let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex)
}
|> ignoreValues
)
disposable.set(signal.start())
}
})
return ActionDisposable {
updateFeaturedDisposable.dispose()
let disposables = helper.with { helper -> [Disposable] in
return helper.reset()
}
for disposable in disposables {
disposable.dispose()
}
disposable.dispose()
}
}
}
private func synchronizeChatListFilters(transaction: Transaction, postbox: Postbox, network: Network, operation: SynchronizeChatListFiltersOperation) -> Signal<Never, NoError> {
switch operation.content {
case .sync:
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
let localFilters = settings.filters
let locallyKnownRemoteFilters = settings.remoteFilters ?? []
return requestChatListFilters(postbox: postbox, network: network)
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
return .complete()
}
|> mapToSignal { remoteFilters -> Signal<Never, NoError> in
if localFilters == locallyKnownRemoteFilters {
return postbox.transaction { transaction -> Void in
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.filters = remoteFilters
state.remoteFilters = state.filters
return state
})
}
|> ignoreValues
}
let locallyKnownRemoteFilterIds = locallyKnownRemoteFilters.map { $0.id }
let remoteFilterIds = remoteFilters.map { $0.id }
let remotelyAddedFilters = Set(remoteFilterIds).subtracting(Set(locallyKnownRemoteFilterIds))
let remotelyRemovedFilters = Set(Set(locallyKnownRemoteFilterIds)).subtracting(remoteFilterIds)
var mergedFilters = localFilters
for id in remotelyRemovedFilters {
mergedFilters.removeAll(where: { $0.id == id })
}
for id in remotelyAddedFilters {
if let filter = remoteFilters.first(where: { $0.id == id }) {
if let index = mergedFilters.firstIndex(where: { $0.id == id }) {
mergedFilters[index] = filter
} else {
mergedFilters.append(filter)
}
}
}
let mergedFilterIds = mergedFilters.map { $0.id }
var deleteSignals: Signal<Never, NoError> = .complete()
for filter in remoteFilters {
if !mergedFilterIds.contains(where: { $0 == filter.id }) {
deleteSignals = deleteSignals
|> then(
requestUpdateChatListFilter(postbox: postbox, network: network, id: filter.id, filter: nil)
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
|> ignoreValues
)
}
}
var addSignals: Signal<Never, NoError> = .complete()
for filter in mergedFilters {
if !remoteFilters.contains(where: { $0.id == filter.id }) {
addSignals = addSignals
|> then(
requestUpdateChatListFilter(postbox: postbox, network: network, id: filter.id, filter: filter)
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
|> ignoreValues
)
}
}
let localFilterIds = localFilters.map { $0.id }
let reorderFilters: Signal<Never, NoError>
if mergedFilterIds != remoteFilterIds {
reorderFilters = network.request(Api.functions.messages.updateDialogFiltersOrder(order: mergedFilters.map { $0.id }))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
} else {
reorderFilters = .complete()
}
return deleteSignals
|> then(
addSignals
)
|> then(
reorderFilters
)
|> then(
postbox.transaction { transaction -> Void in
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.filters = mergedFilters
state.remoteFilters = state.filters
return state
})
}
|> ignoreValues
)
}
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_list.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}