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__);
|
||||
|
||||
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]]) {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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] = []
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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 : [] },
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
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
|
||||
}
|
||||
|> 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
|
||||
}
|
||||
|
||||
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 { users -> Signal<Never, NoError> in
|
||||
return postbox.transaction { transaction -> Void in
|
||||
var peers: [Peer] = []
|
||||
for user in users {
|
||||
peers.append(TelegramUser(user: user))
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
}
|
||||
|> 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 settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return settings.filters.map { filter -> (Int32, ChatListFilter) in
|
||||
return (filter.id, filter)
|
||||
}
|
||||
}
|
||||
|> 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()
|
||||
}
|
||||
|> 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 {
|
||||
reorderFilters = .complete()
|
||||
resolveMissingUsers = .complete()
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
deleteSignals
|
||||
)
|
||||
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
|
||||
|> then(
|
||||
addFilters
|
||||
}
|
||||
} 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,8 +478,53 @@ public struct ChatListFiltersState: PreferencesEntry, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @escaping (ChatListFiltersState) -> ChatListFiltersState) -> Signal<ChatListFiltersState, NoError> {
|
||||
return postbox.transaction { transaction -> ChatListFiltersState in
|
||||
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
|
||||
@ -463,7 +533,6 @@ public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @es
|
||||
return updated
|
||||
})
|
||||
return result ?? .default
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatListFeaturedFilter: PostboxCoding, Equatable {
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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