mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Chat folder improvements
This commit is contained in:
parent
ee2a6ca70f
commit
566816d373
@ -498,7 +498,9 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__);
|
|||||||
ASAssertLocked(__instanceLock__);
|
ASAssertLocked(__instanceLock__);
|
||||||
|
|
||||||
UIView *view = nil;
|
UIView *view = nil;
|
||||||
|
bool initializedWithCustomView = false;
|
||||||
if (_viewBlock) {
|
if (_viewBlock) {
|
||||||
|
initializedWithCustomView = true;
|
||||||
view = _viewBlock();
|
view = _viewBlock();
|
||||||
ASDisplayNodeAssertNotNil(view, @"View block returned nil");
|
ASDisplayNodeAssertNotNil(view, @"View block returned nil");
|
||||||
ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view");
|
ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view");
|
||||||
@ -518,6 +520,19 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__);
|
|||||||
_flags.canCallSetNeedsDisplayOfLayer = NO;
|
_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
|
// UIActivityIndicator
|
||||||
if ([_viewClass isSubclassOfClass:[UIActivityIndicatorView class]]
|
if ([_viewClass isSubclassOfClass:[UIActivityIndicatorView class]]
|
||||||
|| [_viewClass isSubclassOfClass:[UIVisualEffectView class]]) {
|
|| [_viewClass isSubclassOfClass:[UIVisualEffectView class]]) {
|
||||||
|
@ -468,10 +468,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
|
||||||
return settings.filters
|
|
||||||
}
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -514,10 +511,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
|
||||||
return settings.filters
|
|
||||||
}
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -531,10 +525,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
|
||||||
return settings.filters
|
|
||||||
}
|
|
||||||
|> deliverOnMainQueue).start(next: { presetList in
|
|> deliverOnMainQueue).start(next: { presetList in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -563,10 +554,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
|
||||||
return settings.filters
|
|
||||||
}
|
|
||||||
|> deliverOnMainQueue).start(next: { presetList in
|
|> deliverOnMainQueue).start(next: { presetList in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -1150,17 +1138,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
}
|
}
|
||||||
strongSelf.processedFeaturedFilters = true
|
strongSelf.processedFeaturedFilters = true
|
||||||
if !featuredState.isSeen && !featuredState.filters.isEmpty {
|
if !featuredState.isSeen && !featuredState.filters.isEmpty {
|
||||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Bool in
|
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||||
if let state = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState {
|
|> deliverOnMainQueue).start(next: { filters in
|
||||||
return !state.filters.isEmpty
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> deliverOnMainQueue).start(next: { hasFilters in
|
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let hasFilters = !filters.isEmpty
|
||||||
if let _ = strongSelf.validLayout, let parentController = strongSelf.parent as? TabBarController, let sourceFrame = parentController.frameForControllerTab(controller: strongSelf) {
|
if let _ = strongSelf.validLayout, let parentController = strongSelf.parent as? TabBarController, let sourceFrame = parentController.frameForControllerTab(controller: strongSelf) {
|
||||||
let absoluteFrame = sourceFrame
|
let absoluteFrame = sourceFrame
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
@ -1295,29 +1278,26 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
|
|
||||||
@objc private func reorderingDonePressed() {
|
@objc private func reorderingDonePressed() {
|
||||||
if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds {
|
if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds {
|
||||||
let _ = (updateChatListFilterSettingsInteractively(postbox: self.context.account.postbox, { state in
|
let _ = (updateChatListFiltersInteractively(postbox: self.context.account.postbox, { stateFilters in
|
||||||
var state = state
|
|
||||||
var updatedFilters: [ChatListFilter] = []
|
var updatedFilters: [ChatListFilter] = []
|
||||||
for id in reorderedFilterIds {
|
for id in reorderedFilterIds {
|
||||||
if let index = state.filters.firstIndex(where: { $0.id == id }) {
|
if let index = stateFilters.firstIndex(where: { $0.id == id }) {
|
||||||
updatedFilters.append(state.filters[index])
|
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 }) {
|
if !updatedFilters.contains(where: { $0.id == filter.id }) {
|
||||||
return filter
|
return filter
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
state.filters = updatedFilters
|
return updatedFilters
|
||||||
return state
|
|
||||||
})
|
})
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = replaceRemoteChatListFilters(account: strongSelf.context.account).start()
|
|
||||||
strongSelf.reloadFilters(firstUpdate: {
|
strongSelf.reloadFilters(firstUpdate: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -1451,12 +1431,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = updateChatListFilterSettingsInteractively(postbox: strongSelf.context.account.postbox, { settings in
|
let _ = updateChatListFiltersInteractively(postbox: strongSelf.context.account.postbox, { filters in
|
||||||
var settings = settings
|
return filters.filter({ $0.id != id })
|
||||||
settings.filters = settings.filters.filter({ $0.id != id })
|
|
||||||
return settings
|
|
||||||
}).start()
|
}).start()
|
||||||
let _ = replaceRemoteChatListFilters(account: strongSelf.context.account).start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id == id {
|
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) {
|
override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
|
||||||
let _ = (combineLatest(queue: .mainQueue(),
|
let _ = (combineLatest(queue: .mainQueue(),
|
||||||
self.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
currentChatListFilters(postbox: self.context.account.postbox),
|
||||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
|
||||||
return settings.filters
|
|
||||||
},
|
|
||||||
chatListFilterItems(context: self.context)
|
chatListFilterItems(context: self.context)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
)
|
)
|
||||||
@ -2350,7 +2324,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
items.append(.action(ContextMenuActionItem(text: presetList.isEmpty ? "Add Folder" : "Edit Folders", icon: { theme in
|
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
|
}, action: { c, f in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
|
@ -539,20 +539,10 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
self.applyItemNodeAsCurrent(id: .all, itemNode: itemNode)
|
self.applyItemNodeAsCurrent(id: .all, itemNode: itemNode)
|
||||||
|
|
||||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
|
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 []
|
return []
|
||||||
}
|
}
|
||||||
var directions: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter]
|
let 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 = []
|
|
||||||
}
|
|
||||||
return directions
|
return directions
|
||||||
}, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0))
|
}, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0))
|
||||||
panRecognizer.delegate = self
|
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 }) {
|
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)
|
let translation = recognizer.translation(in: self.view)
|
||||||
var transitionFraction = translation.x / layout.size.width
|
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
|
self.transitionFraction = transitionFraction + self.transitionFractionOffset
|
||||||
if let currentItemNode = self.currentItemNodeValue {
|
if let currentItemNode = self.currentItemNodeValue {
|
||||||
|
@ -574,21 +574,19 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
|
|||||||
}
|
}
|
||||||
|
|
||||||
if applyAutomatically {
|
if applyAutomatically {
|
||||||
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
|
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
|
||||||
var settings = settings
|
var filters = filters
|
||||||
for i in 0 ..< settings.filters.count {
|
for i in 0 ..< filters.count {
|
||||||
if settings.filters[i].id == filter.id {
|
if filters[i].id == filter.id {
|
||||||
settings.filters[i].data.categories = categories
|
filters[i].data.categories = categories
|
||||||
settings.filters[i].data.includePeers = includePeers
|
filters[i].data.includePeers = includePeers
|
||||||
settings.filters[i].data.excludePeers = settings.filters[i].data.excludePeers.filter { !settings.filters[i].data.includePeers.contains($0) }
|
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()
|
controller?.dismiss()
|
||||||
|
|
||||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
var filter = filter
|
var filter = filter
|
||||||
@ -653,23 +651,21 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
|
|||||||
excludePeers.sort()
|
excludePeers.sort()
|
||||||
|
|
||||||
if applyAutomatically {
|
if applyAutomatically {
|
||||||
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
|
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
|
||||||
var settings = settings
|
var filters = filters
|
||||||
for i in 0 ..< settings.filters.count {
|
for i in 0 ..< filters.count {
|
||||||
if settings.filters[i].id == filter.id {
|
if filters[i].id == filter.id {
|
||||||
settings.filters[i].data.excludeMuted = additionalCategoryIds.contains(AdditionalExcludeCategoryId.muted.rawValue)
|
filters[i].data.excludeMuted = additionalCategoryIds.contains(AdditionalExcludeCategoryId.muted.rawValue)
|
||||||
settings.filters[i].data.excludeRead = additionalCategoryIds.contains(AdditionalExcludeCategoryId.read.rawValue)
|
filters[i].data.excludeRead = additionalCategoryIds.contains(AdditionalExcludeCategoryId.read.rawValue)
|
||||||
settings.filters[i].data.excludeArchived = additionalCategoryIds.contains(AdditionalExcludeCategoryId.archived.rawValue)
|
filters[i].data.excludeArchived = additionalCategoryIds.contains(AdditionalExcludeCategoryId.archived.rawValue)
|
||||||
settings.filters[i].data.excludePeers = excludePeers
|
filters[i].data.excludePeers = excludePeers
|
||||||
settings.filters[i].data.includePeers = settings.filters[i].data.includePeers.filter { !settings.filters[i].data.excludePeers.contains($0) }
|
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()
|
controller?.dismiss()
|
||||||
|
|
||||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
var filter = filter
|
var filter = filter
|
||||||
@ -921,39 +917,37 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
var applyImpl: (() -> Void)? = {
|
var applyImpl: (() -> Void)? = {
|
||||||
let state = stateValue.with { $0 }
|
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 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
|
var preset = preset
|
||||||
if currentPreset == nil {
|
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 {
|
if let _ = currentPreset {
|
||||||
var found = false
|
var found = false
|
||||||
for i in 0 ..< settings.filters.count {
|
for i in 0 ..< filters.count {
|
||||||
if settings.filters[i].id == preset.id {
|
if filters[i].id == preset.id {
|
||||||
settings.filters[i] = preset
|
filters[i] = preset
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
settings.filters = settings.filters.filter { listFilter in
|
filters = filters.filter { listFilter in
|
||||||
if listFilter.title == preset.title && listFilter.data == preset.data {
|
if listFilter.title == preset.title && listFilter.data == preset.data {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
settings.filters.append(preset)
|
filters.append(preset)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
settings.filters.append(preset)
|
filters.append(preset)
|
||||||
}
|
}
|
||||||
return settings
|
return filters
|
||||||
})
|
})
|
||||||
|> deliverOnMainQueue).start(next: { settings in
|
|> deliverOnMainQueue).start(next: { filters in
|
||||||
updated(settings.filters)
|
updated(filters)
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
|
|
||||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,13 +248,13 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||||||
|
|
||||||
let arguments = ChatListFilterPresetListControllerArguments(context: context,
|
let arguments = ChatListFilterPresetListControllerArguments(context: context,
|
||||||
addSuggestedPresed: { title, data in
|
addSuggestedPresed: { title, data in
|
||||||
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
|
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
|
||||||
var settings = settings
|
var filters = filters
|
||||||
settings.filters.insert(ChatListFilter(id: max(2, settings.filters.map({ $0.id + 1 }).max() ?? 2), title: title, data: data), at: 0)
|
let id = generateNewChatListFilterId(filters: filters)
|
||||||
return settings
|
filters.insert(ChatListFilter(id: id, title: title, data: data), at: 0)
|
||||||
|
return filters
|
||||||
})
|
})
|
||||||
|> deliverOnMainQueue).start(next: { settings in
|
|> deliverOnMainQueue).start(next: { _ in
|
||||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
|
||||||
})
|
})
|
||||||
}, openPreset: { preset in
|
}, openPreset: { preset in
|
||||||
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: { _ in }))
|
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: { _ in }))
|
||||||
@ -269,25 +269,20 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}, removePreset: { id in
|
}, removePreset: { id in
|
||||||
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
|
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
|
||||||
var settings = settings
|
var filters = filters
|
||||||
if let index = settings.filters.firstIndex(where: { $0.id == id }) {
|
if let index = filters.firstIndex(where: { $0.id == id }) {
|
||||||
settings.filters.remove(at: index)
|
filters.remove(at: index)
|
||||||
}
|
}
|
||||||
return settings
|
return filters
|
||||||
})
|
})
|
||||||
|> deliverOnMainQueue).start(next: { settings in
|
|> deliverOnMainQueue).start(next: { _ in
|
||||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:])
|
let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:])
|
||||||
|
|
||||||
let filtersWithCountsSignal = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
let filtersWithCountsSignal = updatedChatListFilters(postbox: context.account.postbox)
|
||||||
|> map { preferences -> [ChatListFilter] in
|
|
||||||
let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
|
|
||||||
return filtersState.filters
|
|
||||||
}
|
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in
|
|> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in
|
||||||
return .single(filters.map { filter -> (ChatListFilter, Int) in
|
return .single(filters.map { filter -> (ChatListFilter, Int) in
|
||||||
@ -355,24 +350,20 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak updatedFilterOrder] updatedFilterOrderValue in
|
|> deliverOnMainQueue).start(next: { [weak updatedFilterOrder] updatedFilterOrderValue in
|
||||||
if let updatedFilterOrderValue = updatedFilterOrderValue {
|
if let updatedFilterOrderValue = updatedFilterOrderValue {
|
||||||
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { filtersState in
|
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
|
||||||
var filtersState = filtersState
|
|
||||||
|
|
||||||
var updatedFilters: [ChatListFilter] = []
|
var updatedFilters: [ChatListFilter] = []
|
||||||
for id in updatedFilterOrderValue {
|
for id in updatedFilterOrderValue {
|
||||||
if let index = filtersState.filters.firstIndex(where: { $0.id == id }) {
|
if let index = filters.firstIndex(where: { $0.id == id }) {
|
||||||
updatedFilters.append(filtersState.filters[index])
|
updatedFilters.append(filters[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for filter in filtersState.filters {
|
for filter in filters {
|
||||||
if !updatedFilters.contains(where: { $0.id == filter.id }) {
|
if !updatedFilters.contains(where: { $0.id == filter.id }) {
|
||||||
updatedFilters.append(filter)
|
updatedFilters.append(filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filtersState.filters = updatedFilters
|
return updatedFilters
|
||||||
|
|
||||||
return filtersState
|
|
||||||
})
|
})
|
||||||
|> deliverOnMainQueue).start(next: { _ in
|
|> deliverOnMainQueue).start(next: { _ in
|
||||||
filtersWithCounts.set(filtersWithCountsSignal)
|
filtersWithCounts.set(filtersWithCountsSignal)
|
||||||
@ -425,7 +416,6 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||||||
controller.navigationPresentation = .modal
|
controller.navigationPresentation = .modal
|
||||||
}
|
}
|
||||||
controller.didDisappear = { _ in
|
controller.didDisappear = { _ in
|
||||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
|
||||||
dismissed?()
|
dismissed?()
|
||||||
}
|
}
|
||||||
pushControllerImpl = { [weak controller] c in
|
pushControllerImpl = { [weak controller] c in
|
||||||
|
@ -697,10 +697,10 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
let minSpacing: CGFloat = 26.0
|
let minSpacing: CGFloat = 26.0
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
let resolvedSideInset: CGFloat = 16.0 + sideInset
|
||||||
var leftOffset: CGFloat = sideInset
|
var leftOffset: CGFloat = resolvedSideInset
|
||||||
|
|
||||||
var longTitlesWidth: CGFloat = sideInset
|
var longTitlesWidth: CGFloat = resolvedSideInset
|
||||||
for i in 0 ..< tabSizes.count {
|
for i in 0 ..< tabSizes.count {
|
||||||
let (itemId, paneNodeSize, paneNodeShortSize, paneNode, wasAdded) = tabSizes[i]
|
let (itemId, paneNodeSize, paneNodeShortSize, paneNode, wasAdded) = tabSizes[i]
|
||||||
longTitlesWidth += paneNodeSize.width
|
longTitlesWidth += paneNodeSize.width
|
||||||
@ -708,7 +708,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||||||
longTitlesWidth += minSpacing
|
longTitlesWidth += minSpacing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
longTitlesWidth += sideInset
|
longTitlesWidth += resolvedSideInset
|
||||||
let useShortTitles = longTitlesWidth > size.width
|
let useShortTitles = longTitlesWidth > size.width
|
||||||
|
|
||||||
for i in 0 ..< tabSizes.count {
|
for i in 0 ..< tabSizes.count {
|
||||||
@ -746,7 +746,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
|||||||
leftOffset += paneNodeSize.width + minSpacing
|
leftOffset += paneNodeSize.width + minSpacing
|
||||||
}
|
}
|
||||||
leftOffset -= minSpacing
|
leftOffset -= minSpacing
|
||||||
leftOffset += sideInset
|
leftOffset += resolvedSideInset
|
||||||
|
|
||||||
self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height)
|
self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height)
|
||||||
|
|
||||||
|
@ -1665,7 +1665,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let leftInset: CGFloat = params.leftInset + avatarLeftInset
|
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)
|
let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + offset, dy: 0.0)
|
||||||
|
|
||||||
|
@ -1243,15 +1243,10 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func resetFilter() {
|
private func resetFilter() {
|
||||||
if let chatListFilter = chatListFilter {
|
if let chatListFilter = self.chatListFilter {
|
||||||
let preferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.chatListFilters]))
|
self.updatedFilterDisposable.set((updatedChatListFilters(postbox: self.context.account.postbox)
|
||||||
self.updatedFilterDisposable.set((context.account.postbox.combinedView(keys: [preferencesKey])
|
|> map { filters -> ChatListFilter? in
|
||||||
|> map { view -> ChatListFilter? in
|
for filter in filters {
|
||||||
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 filter.id == chatListFilter.id {
|
if filter.id == chatListFilter.id {
|
||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
|
@ -11,15 +11,7 @@ import TelegramUIPreferences
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilter, Int)]), NoError> {
|
func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilter, Int)]), NoError> {
|
||||||
let preferencesKey: PostboxViewKey = .preferences(keys: [PreferencesKeys.chatListFilters])
|
return updatedChatListFilters(postbox: context.account.postbox)
|
||||||
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 []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { filters -> Signal<(Int, [(ChatListFilter, Int)]), NoError> in
|
|> mapToSignal { filters -> Signal<(Int, [(ChatListFilter, Int)]), NoError> in
|
||||||
var unreadCountItems: [UnreadMessageCountsItem] = []
|
var unreadCountItems: [UnreadMessageCountsItem] = []
|
||||||
|
@ -1492,6 +1492,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = .Hide
|
self.statusBar.statusBarStyle = .Hide
|
||||||
|
self.lockOrientation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
required init(coder aDecoder: NSCoder) {
|
||||||
|
@ -873,8 +873,14 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
let initialX: CGFloat = backButtonInset
|
let initialX: CGFloat = backButtonInset
|
||||||
let finalX: CGFloat = floor((size.width - backButtonSize.width) / 2.0) - size.width
|
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)
|
let backButtonFrame = 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)
|
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 {
|
if let transitionTitleNode = self.transitionTitleNode {
|
||||||
let transitionTitleSize = transitionTitleNode.measure(CGSize(width: size.width, height: nominalHeight))
|
let transitionTitleSize = transitionTitleNode.measure(CGSize(width: size.width, height: nominalHeight))
|
||||||
|
@ -138,6 +138,7 @@ public struct OperationLogTags {
|
|||||||
public static let SynchronizeRecentlyUsedStickers = PeerOperationLogTag(value: 17)
|
public static let SynchronizeRecentlyUsedStickers = PeerOperationLogTag(value: 17)
|
||||||
public static let SynchronizeAppLogEvents = PeerOperationLogTag(value: 18)
|
public static let SynchronizeAppLogEvents = PeerOperationLogTag(value: 18)
|
||||||
public static let SynchronizeEmojiKeywords = PeerOperationLogTag(value: 19)
|
public static let SynchronizeEmojiKeywords = PeerOperationLogTag(value: 19)
|
||||||
|
public static let SynchronizeChatListFilters = PeerOperationLogTag(value: 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
||||||
|
@ -1044,7 +1044,7 @@ public class Account {
|
|||||||
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
||||||
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network))
|
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network).start())
|
||||||
|
|
||||||
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
||||||
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
||||||
|
@ -97,6 +97,9 @@ enum AccountStateMutationOperation {
|
|||||||
case UpdatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool)
|
case UpdatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool)
|
||||||
case UpdatePeersNearby([PeerNearby])
|
case UpdatePeersNearby([PeerNearby])
|
||||||
case UpdateTheme(TelegramTheme)
|
case UpdateTheme(TelegramTheme)
|
||||||
|
case SyncChatListFilters
|
||||||
|
case UpdateChatListFilterOrder(order: [Int32])
|
||||||
|
case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AccountMutableState {
|
struct AccountMutableState {
|
||||||
@ -408,9 +411,21 @@ struct AccountMutableState {
|
|||||||
self.addOperation(.UpdateCall(call))
|
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) {
|
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||||
switch operation {
|
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
|
break
|
||||||
case let .AddMessages(messages, location):
|
case let .AddMessages(messages, location):
|
||||||
for message in messages {
|
for message in messages {
|
||||||
|
@ -156,6 +156,7 @@ private var declaredEncodables: Void = {
|
|||||||
declareEncodable(PeersNearbyState.self, f: { PeersNearbyState(decoder: $0) })
|
declareEncodable(PeersNearbyState.self, f: { PeersNearbyState(decoder: $0) })
|
||||||
declareEncodable(TelegramMediaDice.self, f: { TelegramMediaDice(decoder: $0) })
|
declareEncodable(TelegramMediaDice.self, f: { TelegramMediaDice(decoder: $0) })
|
||||||
declareEncodable(ChatListFiltersFeaturedState.self, f: { ChatListFiltersFeaturedState(decoder: $0) })
|
declareEncodable(ChatListFiltersFeaturedState.self, f: { ChatListFiltersFeaturedState(decoder: $0) })
|
||||||
|
declareEncodable(SynchronizeChatListFiltersOperation.self, f: { SynchronizeChatListFiltersOperation(decoder: $0) })
|
||||||
|
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
@ -1320,6 +1320,12 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
}
|
}
|
||||||
case let .updateMessageID(id, randomId):
|
case let .updateMessageID(id, randomId):
|
||||||
updatedState.updatedOutgoingUniqueMessageIds[randomId] = id
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -2042,7 +2048,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
|||||||
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
||||||
for operation in operations {
|
for operation in operations {
|
||||||
switch operation {
|
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 {
|
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||||
}
|
}
|
||||||
@ -2126,6 +2132,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||||||
var updatedThemes: [Int64: TelegramTheme] = [:]
|
var updatedThemes: [Int64: TelegramTheme] = [:]
|
||||||
var delayNotificatonsUntil: Int32?
|
var delayNotificatonsUntil: Int32?
|
||||||
var peerActivityTimestamps: [PeerId: Int32] = [:]
|
var peerActivityTimestamps: [PeerId: Int32] = [:]
|
||||||
|
var syncChatListFilters = false
|
||||||
|
|
||||||
var holesFromPreviousStateMessageIds: [MessageId] = []
|
var holesFromPreviousStateMessageIds: [MessageId] = []
|
||||||
|
|
||||||
@ -2683,6 +2690,46 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||||||
updatedPeersNearby = peersNearby
|
updatedPeersNearby = peersNearby
|
||||||
case let .UpdateTheme(theme):
|
case let .UpdateTheme(theme):
|
||||||
updatedThemes[theme.id] = 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)
|
return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil)
|
||||||
}
|
}
|
||||||
|
@ -226,8 +226,8 @@ public enum RequestUpdateChatListFilterError {
|
|||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestUpdateChatListFilter(account: Account, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
|
public func requestUpdateChatListFilter(postbox: Postbox, network: Network, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
|
||||||
return account.postbox.transaction { transaction -> Api.DialogFilter? in
|
return postbox.transaction { transaction -> Api.DialogFilter? in
|
||||||
return filter?.apiFilter(transaction: transaction)
|
return filter?.apiFilter(transaction: transaction)
|
||||||
}
|
}
|
||||||
|> castError(RequestUpdateChatListFilterError.self)
|
|> castError(RequestUpdateChatListFilterError.self)
|
||||||
@ -236,7 +236,7 @@ public func requestUpdateChatListFilter(account: Account, id: Int32, filter: Cha
|
|||||||
if inputFilter != nil {
|
if inputFilter != nil {
|
||||||
flags |= 1 << 0
|
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
|
|> mapError { _ -> RequestUpdateChatListFilterError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
@ -324,118 +324,143 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|
|||||||
|> castError(RequestChatListFiltersError.self)
|
|> castError(RequestChatListFiltersError.self)
|
||||||
|> mapToSignal { filtersAndMissingPeers -> Signal<[ChatListFilter], RequestChatListFiltersError> in
|
|> mapToSignal { filtersAndMissingPeers -> Signal<[ChatListFilter], RequestChatListFiltersError> in
|
||||||
let (filters, missingPeers) = filtersAndMissingPeers
|
let (filters, missingPeers) = filtersAndMissingPeers
|
||||||
return .single(filters)
|
|
||||||
}
|
var missingUsers: [Api.InputUser] = []
|
||||||
}
|
var missingChannels: [Api.InputChannel] = []
|
||||||
}
|
var missingGroups: [Int32] = []
|
||||||
|
for peer in missingPeers {
|
||||||
func managedChatListFilters(postbox: Postbox, network: Network) -> Disposable {
|
switch peer {
|
||||||
let disposables = DisposableSet()
|
case let .inputPeerUser(userId, accessHash):
|
||||||
disposables.add(updateChatListFeaturedFilters(postbox: postbox, network: network).start())
|
missingUsers.append(.inputUser(userId: userId, accessHash: accessHash))
|
||||||
|
case .inputPeerSelf:
|
||||||
disposables.add((requestChatListFilters(postbox: postbox, network: network)
|
missingUsers.append(.inputUserSelf)
|
||||||
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
|
case let .inputPeerChannel(channelId, accessHash):
|
||||||
return .complete()
|
missingChannels.append(.inputChannel(channelId: channelId, accessHash: accessHash))
|
||||||
}
|
case let .inputPeerChat(id):
|
||||||
|> mapToSignal { filters -> Signal<Never, NoError> in
|
missingGroups.append(id)
|
||||||
return postbox.transaction { transaction in
|
case .inputPeerEmpty:
|
||||||
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
|
break
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let addFilters = account.postbox.transaction { transaction -> [(Int32, ChatListFilter)] in
|
let resolveMissingUsers: Signal<Never, NoError>
|
||||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
if !missingUsers.isEmpty {
|
||||||
return settings.filters.map { filter -> (Int32, ChatListFilter) in
|
resolveMissingUsers = network.request(Api.functions.users.getUsers(id: missingUsers))
|
||||||
return (filter.id, filter)
|
|> `catch` { _ -> Signal<[Api.User], NoError> in
|
||||||
|
return .single([])
|
||||||
}
|
}
|
||||||
}
|
|> mapToSignal { users -> Signal<Never, NoError> in
|
||||||
|> mapToSignal { filters -> Signal<Never, NoError> in
|
return postbox.transaction { transaction -> Void in
|
||||||
var signals: [Signal<Never, NoError>] = []
|
var peers: [Peer] = []
|
||||||
for (id, filter) in filters {
|
for user in users {
|
||||||
if !remoteFilters.contains(filter) {
|
peers.append(TelegramUser(user: user))
|
||||||
signals.append(requestUpdateChatListFilter(account: account, id: id, filter: filter)
|
|
||||||
|> `catch` { _ -> Signal<Never, NoError> in
|
|
||||||
return .complete()
|
|
||||||
}
|
}
|
||||||
|> ignoreValues)
|
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
|
||||||
|
return updated
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
|> ignoreValues
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reorderFilters = .complete()
|
resolveMissingUsers = .complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
return combineLatest(
|
let resolveMissingChannels: Signal<Never, NoError>
|
||||||
deleteSignals
|
if !missingChannels.isEmpty {
|
||||||
)
|
resolveMissingChannels = network.request(Api.functions.channels.getChannels(id: missingChannels))
|
||||||
|> ignoreValues
|
|> map(Optional.init)
|
||||||
|> then(
|
|> `catch` { _ -> Signal<Api.messages.Chats?, NoError> in
|
||||||
addFilters
|
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(
|
|> then(
|
||||||
reorderFilters
|
resolveMissingChannels
|
||||||
|
)
|
||||||
|
|> then(
|
||||||
|
resolveMissingGroups
|
||||||
|
)
|
||||||
|
|> castError(RequestChatListFiltersError.self)
|
||||||
|
|> mapToSignal { _ -> Signal<[ChatListFilter], RequestChatListFiltersError> in
|
||||||
|
}
|
||||||
|
|> then(
|
||||||
|
.single(filters)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ChatListFiltersState: PreferencesEntry, Equatable {
|
struct ChatListFiltersState: PreferencesEntry, Equatable {
|
||||||
public var filters: [ChatListFilter]
|
var filters: [ChatListFilter]
|
||||||
public var remoteFilters: [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.filters = filters
|
||||||
self.remoteFilters = remoteFilters
|
self.remoteFilters = remoteFilters
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
init(decoder: PostboxDecoder) {
|
||||||
self.filters = decoder.decodeObjectArrayWithDecoderForKey("filters")
|
self.filters = decoder.decodeObjectArrayWithDecoderForKey("filters")
|
||||||
self.remoteFilters = decoder.decodeOptionalObjectArrayWithDecoderForKey("remoteFilters")
|
self.remoteFilters = decoder.decodeOptionalObjectArrayWithDecoderForKey("remoteFilters")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
func encode(_ encoder: PostboxEncoder) {
|
||||||
encoder.encodeObjectArray(self.filters, forKey: "filters")
|
encoder.encodeObjectArray(self.filters, forKey: "filters")
|
||||||
if let remoteFilters = self.remoteFilters {
|
if let remoteFilters = self.remoteFilters {
|
||||||
encoder.encodeObjectArray(remoteFilters, forKey: "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 {
|
if let to = to as? ChatListFiltersState, self == to {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@ -453,19 +478,63 @@ public struct ChatListFiltersState: PreferencesEntry, Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @escaping (ChatListFiltersState) -> ChatListFiltersState) -> Signal<ChatListFiltersState, NoError> {
|
public func generateNewChatListFilterId(filters: [ChatListFilter]) -> Int32 {
|
||||||
return postbox.transaction { transaction -> ChatListFiltersState in
|
while true {
|
||||||
var result: ChatListFiltersState?
|
let id = Int32(2 + arc4random_uniform(255 - 2))
|
||||||
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
|
if !filters.contains(where: { $0.id == id }) {
|
||||||
let settings = entry as? ChatListFiltersState ?? ChatListFiltersState.default
|
return id
|
||||||
let updated = f(settings)
|
}
|
||||||
result = updated
|
|
||||||
return updated
|
|
||||||
})
|
|
||||||
return result ?? .default
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 struct ChatListFeaturedFilter: PostboxCoding, Equatable {
|
||||||
public var title: String
|
public var title: String
|
||||||
public var description: String
|
public var description: String
|
||||||
@ -579,3 +648,293 @@ public func updateChatListFeaturedFilters(postbox: Postbox, network: Network) ->
|
|||||||
|> ignoreValues
|
|> 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ItemList.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ItemList.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "ic_list.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ItemList.imageset/ic_list.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ItemList.imageset/ic_list.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user