Просмотр исходного кода

implement ChatListViewModel to have a similar search as in main app

cyberta 5 лет назад
Родитель
Сommit
6db9eb271b

+ 44 - 8
DcShare/Controller/ChatListController.swift

@@ -8,7 +8,7 @@ protocol ChatListDelegate: class {
 
 class ChatListController: UITableViewController {
     let dcContext: DcContext
-    var chatList: DcChatlist?
+    let viewModel: ChatListViewModel
     let contactCellReuseIdentifier = "contactCellReuseIdentifier"
     weak var chatListDelegate: ChatListDelegate?
 
@@ -16,18 +16,21 @@ class ChatListController: UITableViewController {
 
     private lazy var searchController: UISearchController = {
         let searchController = UISearchController(searchResultsController: nil)
-        //searchController.searchResultsUpdater = self
+        searchController.searchResultsUpdater = viewModel
         searchController.obscuresBackgroundDuringPresentation = false
         searchController.searchBar.placeholder = String.localized("search")
         searchController.dimsBackgroundDuringPresentation = false
         searchController.hidesNavigationBarDuringPresentation = true
+        searchController.searchBar.delegate = self
         return searchController
     }()
 
     init(dcContext: DcContext, chatListDelegate: ChatListDelegate) {
         self.dcContext = dcContext
         self.chatListDelegate = chatListDelegate
+        viewModel = ChatListViewModel(dcContext: dcContext)
         super.init(style: .grouped)
+        viewModel.onChatListUpdate = handleChatListUpdate
     }
 
     required init?(coder: NSCoder) {
@@ -45,7 +48,7 @@ class ChatListController: UITableViewController {
 
     override func viewDidLoad() {
         super.viewDidLoad()
-        chatList = dcContext.getChatlist(flags: DC_GCL_ADD_ALLDONE_HINT | DC_GCL_FOR_FORWARDING | DC_GCL_NO_SPECIALS, queryString: nil, queryId: 0)
+        //chatList = dcContext.getChatlist(flags: DC_GCL_ADD_ALLDONE_HINT | DC_GCL_FOR_FORWARDING | DC_GCL_NO_SPECIALS, queryString: nil, queryId: 0)
         navigationItem.searchController = searchController
         tableView.register(ChatListCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
         tableView.rowHeight = 64
@@ -54,8 +57,12 @@ class ChatListController: UITableViewController {
 
     }
 
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return viewModel.numberOfSections
+    }
+
     override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-        return chatList?.length ?? 0
+        return viewModel.numberOfRowsIn(section: section)
     }
 
     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@@ -63,17 +70,46 @@ class ChatListController: UITableViewController {
             fatalError("could not deque TableViewCell")
         }
 
-        if let chatList = chatList {
-            cell.updateCell(chatId: chatList.getChatId(index: indexPath.row))
+        if let chatId = viewModel.getChatId(section: indexPath.section, row: indexPath.row) {
+            cell.updateCell(chatId: chatId)
         }
 
         return cell
     }
 
     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        if let chatList = chatList {
-            chatListDelegate?.onChatSelected(chatId: chatList.getChatId(index: indexPath.row))
+        if let chatId = viewModel.getChatId(section: indexPath.section, row: indexPath.row) {
+            chatListDelegate?.onChatSelected(chatId: chatId)
         }
     }
 
+    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+           return viewModel.titleForHeaderIn(section: section)
+       }
+
+    func handleChatListUpdate() {
+        tableView.reloadData()
+    }
+
+}
+
+// MARK: - uisearchbardelegate
+extension ChatListController: UISearchBarDelegate {
+    func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
+        viewModel.beginSearch()
+        return true
+    }
+
+    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
+        // searchBar will be set to "" by system
+        viewModel.endSearch()
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
+           self.tableView.scrollToTop()
+        }
+    }
+
+    func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
+        tableView.scrollToTop()
+        return true
+    }
 }

+ 167 - 0
DcShare/ViewModel/ChatListViewModel.swift

@@ -0,0 +1,167 @@
+import UIKit
+import DcCore
+
+
+// MARK: - ChatListViewModel
+class ChatListViewModel: NSObject {
+
+    var onChatListUpdate: VoidFunction?
+
+    enum ChatListSectionType {
+        case chats
+        case contacts
+    }
+
+    private let dcContext: DcContext
+
+    var searchActive: Bool = false
+
+    // if searchfield is empty we show default chat list
+    private var showSearchResults: Bool {
+        return searchActive && searchText.containsCharacters()
+    }
+
+    private var chatList: DcChatlist!
+
+    // for search filtering
+    private var searchText: String = ""
+    private var searchResultChatList: DcChatlist?
+    private var searchResultContactIds: [Int] = []
+
+    // to manage sections dynamically
+    private var searchResultsChatsSection: ChatListSectionType = .chats
+    private var searchResultsContactsSection: ChatListSectionType = .contacts
+    private var searchResultSections: [ChatListSectionType] = []
+
+    init(dcContext: DcContext) {
+        self.dcContext = dcContext
+        super.init()
+        updateChatList(notifyListener: true)
+    }
+
+    private func updateChatList(notifyListener: Bool) {
+        self.chatList = dcContext.getChatlist(flags: DC_GCL_ADD_ALLDONE_HINT | DC_GCL_FOR_FORWARDING | DC_GCL_NO_SPECIALS, queryString: nil, queryId: 0)
+        if notifyListener {
+            onChatListUpdate?()
+        }
+    }
+
+    func getChatId(section: Int, row: Int) -> Int? {
+        if showSearchResults {
+            switch searchResultSections[section] {
+            case .chats:
+                let list: DcChatlist? = searchResultChatList
+                return list?.getChatId(index: row)
+            case .contacts:
+                return searchResultContactIds[row]
+            }
+        }
+        return chatList.getChatId(index: row)
+    }
+
+    var numberOfSections: Int {
+        if showSearchResults {
+            return searchResultSections.count
+        }
+        return 1
+    }
+
+    func numberOfRowsIn(section: Int) -> Int {
+        if showSearchResults {
+            switch searchResultSections[section] {
+            case .chats:
+                return searchResultChatList?.length ?? 0
+            case .contacts:
+                return searchResultContactIds.count
+            }
+        }
+        return chatList.length
+    }
+
+    func titleForHeaderIn(section: Int) -> String? {
+        if showSearchResults {
+            let title: String
+            switch searchResultSections[section] {
+            case .chats:
+                title = "n_chats"
+            case .contacts:
+                title = "n_contacts"
+            }
+            return String.localized(stringID: title, count: numberOfRowsIn(section: section))
+        }
+        return nil
+    }
+
+    func refreshData() {
+        updateChatList(notifyListener: true)
+    }
+
+    func beginSearch() {
+        searchActive = true
+    }
+
+    func endSearch() {
+        searchActive = false
+        searchText = ""
+        resetSearch()
+    }
+
+    var emptySearchText: String? {
+        if searchActive && numberOfSections == 0 {
+            return searchText
+        }
+        return nil
+    }
+
+    // MARK: - search
+    func updateSearchResultSections() {
+        var sections: [ChatListSectionType] = []
+        if let chatList = searchResultChatList, chatList.length > 0 {
+            sections.append(searchResultsChatsSection)
+        }
+        if !searchResultContactIds.isEmpty {
+            sections.append(searchResultsContactsSection)
+        }
+        searchResultSections = sections
+    }
+
+    func resetSearch() {
+        searchResultChatList = nil
+        searchResultContactIds = []
+        updateSearchResultSections()
+    }
+
+    func filterContentForSearchText(_ searchText: String) {
+           if !searchText.isEmpty {
+               filterAndUpdateList(searchText: searchText)
+           } else {
+               // when search input field empty we show default chatList
+               resetSearch()
+           }
+           onChatListUpdate?()
+       }
+
+    func filterAndUpdateList(searchText: String) {
+
+           // #1 chats with searchPattern in title bar
+           var flags: Int32 = 0
+           flags |= DC_GCL_NO_SPECIALS
+           searchResultChatList = dcContext.getChatlist(flags: flags, queryString: searchText, queryId: 0)
+
+           // #2 contacts with searchPattern in name or in email
+           searchResultContactIds = dcContext.getContacts(flags: DC_GCL_ADD_SELF, queryString: searchText)
+
+           updateSearchResultSections()
+       }
+}
+
+// MARK: UISearchResultUpdating
+extension ChatListViewModel: UISearchResultsUpdating {
+    func updateSearchResults(for searchController: UISearchController) {
+        self.searchText = searchController.searchBar.text ?? ""
+        if let searchText = searchController.searchBar.text {
+            filterContentForSearchText(searchText)
+            return
+        }
+    }
+}

+ 14 - 4
deltachat-ios.xcodeproj/project.pbxproj

@@ -23,6 +23,8 @@
 		304219D3243F588500516852 /* DcCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 304219D1243F588500516852 /* DcCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		304219D92440734A00516852 /* DcMsg+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304219D82440734A00516852 /* DcMsg+Extension.swift */; };
 		304F5E44244F571C00462538 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A9FB14A1FB061E2001FEA36 /* Assets.xcassets */; };
+		3057027F24C5B2F800D84EFC /* ChatListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3057027E24C5B2F800D84EFC /* ChatListViewModel.swift */; };
+		3057028724C5C88300D84EFC /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; };
 		305961CC2346125100C80F33 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961822346125000C80F33 /* UIView+Extensions.swift */; };
 		305961CD2346125100C80F33 /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961832346125000C80F33 /* UIEdgeInsets+Extensions.swift */; };
 		305961CF2346125100C80F33 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961852346125000C80F33 /* UIColor+Extensions.swift */; };
@@ -180,7 +182,6 @@
 		AEE700252438E0E500D6992E /* ProgressAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE700242438E0E500D6992E /* ProgressAlertHandler.swift */; };
 		AEF53BD5248904BF00D309C1 /* GalleryTimeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEF53BD4248904BF00D309C1 /* GalleryTimeLabel.swift */; };
 		AEFBE22F23FEF23D0045327A /* ProviderInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFBE22E23FEF23D0045327A /* ProviderInfoCell.swift */; };
-		AEFBE23123FF09B20045327A /* TypeAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFBE23023FF09B20045327A /* TypeAlias.swift */; };
 		B20462E42440A4A600367A57 /* SettingsAutodelOverviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20462E32440A4A600367A57 /* SettingsAutodelOverviewController.swift */; };
 		B20462E62440C99600367A57 /* SettingsAutodelSetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20462E52440C99600367A57 /* SettingsAutodelSetController.swift */; };
 		B21005DB23383664004C70C5 /* SettingsClassicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21005DA23383664004C70C5 /* SettingsClassicViewController.swift */; };
@@ -265,6 +266,7 @@
 		3040F461234F550300FA34D5 /* AudioPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerView.swift; sourceTree = "<group>"; };
 		304219D1243F588500516852 /* DcCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DcCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		304219D82440734A00516852 /* DcMsg+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DcMsg+Extension.swift"; sourceTree = "<group>"; };
+		3057027E24C5B2F800D84EFC /* ChatListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListViewModel.swift; sourceTree = "<group>"; };
 		305961822346125000C80F33 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = "<group>"; };
 		305961832346125000C80F33 /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = "<group>"; };
 		305961852346125000C80F33 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
@@ -472,7 +474,6 @@
 		AEE700242438E0E500D6992E /* ProgressAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressAlertHandler.swift; sourceTree = "<group>"; };
 		AEF53BD4248904BF00D309C1 /* GalleryTimeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryTimeLabel.swift; sourceTree = "<group>"; };
 		AEFBE22E23FEF23D0045327A /* ProviderInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderInfoCell.swift; sourceTree = "<group>"; };
-		AEFBE23023FF09B20045327A /* TypeAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeAlias.swift; sourceTree = "<group>"; };
 		B20462E02440805C00367A57 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		B20462E12440805C00367A57 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
 		B20462E22440805C00367A57 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@@ -581,6 +582,14 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		3057027D24C5B2C700D84EFC /* ViewModel */ = {
+			isa = PBXGroup;
+			children = (
+				3057027E24C5B2F800D84EFC /* ChatListViewModel.swift */,
+			);
+			path = ViewModel;
+			sourceTree = "<group>";
+		};
 		3059617E234610A800C80F33 /* MessageKit */ = {
 			isa = PBXGroup;
 			children = (
@@ -742,6 +751,7 @@
 		30E8F2112447285600CE2C90 /* DcShare */ = {
 			isa = PBXGroup;
 			children = (
+				3057027D24C5B2C700D84EFC /* ViewModel */,
 				304F5E472451D2CA00462538 /* View */,
 				304F5E462451D2AA00462538 /* Controller */,
 				304F5E452451D27500462538 /* Helper */,
@@ -953,7 +963,6 @@
 				30AC265E237F1807002A943F /* AvatarHelper.swift */,
 				302B84C42396627F001C261F /* RelayHelper.swift */,
 				AE1988A423EB2FBA00B4CD5F /* Errors.swift */,
-				AEFBE23023FF09B20045327A /* TypeAlias.swift */,
 				307D822D241669C7006D2490 /* LocationManager.swift */,
 				AE0AA9552478191900D42A7F /* GridCollectionViewFlowLayout.swift */,
 				AE6EC5272497B9B200A400E4 /* ThumbnailCache.swift */,
@@ -1157,6 +1166,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				30E8F2162447285600CE2C90 /* MainInterface.storyboard in Resources */,
+				3057028724C5C88300D84EFC /* Localizable.stringsdict in Resources */,
 				304F5E44244F571C00462538 /* Assets.xcassets in Resources */,
 				30E8F2512449EA0E00CE2C90 /* Localizable.strings in Resources */,
 			);
@@ -1340,6 +1350,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				3057027F24C5B2F800D84EFC /* ChatListViewModel.swift in Sources */,
 				302589FF2452FA280086C1CD /* ShareAttachment.swift in Sources */,
 				30E8F2442449C64100CE2C90 /* ChatListCell.swift in Sources */,
 				30E8F2132447285600CE2C90 /* ShareViewController.swift in Sources */,
@@ -1417,7 +1428,6 @@
 				3059620B2346125100C80F33 /* LocationMessageSizeCalculator.swift in Sources */,
 				305962072346125100C80F33 /* MessagesCollectionViewFlowLayout.swift in Sources */,
 				78ED838321D5379000243125 /* TextFieldCell.swift in Sources */,
-				AEFBE23123FF09B20045327A /* TypeAlias.swift in Sources */,
 				AE19887523EB264000B4CD5F /* HelpViewController.swift in Sources */,
 				305961D52346125100C80F33 /* MessagesViewController+Menu.swift in Sources */,
 				305961F22346125100C80F33 /* LocationMessageCell.swift in Sources */,