Quellcode durchsuchen

load chat list in background, refresh UI once it is loaded

cyberta vor 3 Jahren
Ursprung
Commit
588ed5869d

+ 50 - 33
deltachat-ios/Controller/ChatListController.swift

@@ -2,8 +2,9 @@ import UIKit
 import DcCore
 
 class ChatListController: UITableViewController {
-    let viewModel: ChatListViewModel
+    var viewModel: ChatListViewModel?
     let dcContext: DcContext
+    var isArchive: Bool
 
     private let chatCellReuseIdentifier = "chat_cell"
     private let deadDropCellReuseIdentifier = "deaddrop_cell"
@@ -59,15 +60,18 @@ class ChatListController: UITableViewController {
         return label
     }()
 
-    init(dcContext: DcContext, viewModel: ChatListViewModel) {
-        self.viewModel = viewModel
+    init(dcContext: DcContext, isArchive: Bool) {
         self.dcContext = dcContext
-        if viewModel.isArchive {
-            super.init(style: .grouped)
-        } else {
-            super.init(style: .grouped)
+        self.isArchive = isArchive
+        super.init(style: .grouped)
+        DispatchQueue.global(qos: .userInteractive).async { [weak self] in
+            guard let self = self else { return }
+            self.viewModel = ChatListViewModel(dcContext: self.dcContext, isArchive: isArchive)
+            self.viewModel?.onChatListUpdate = self.handleChatListUpdate
+            DispatchQueue.main.async { [weak self] in
+                self?.tableView.reloadData()
+            }
         }
-        viewModel.onChatListUpdate = handleChatListUpdate // register listener
     }
 
     required init?(coder _: NSCoder) {
@@ -77,7 +81,7 @@ class ChatListController: UITableViewController {
     // MARK: - lifecycle
     override func viewDidLoad() {
         super.viewDidLoad()
-        if !viewModel.isArchive {
+        if isArchive {
             navigationItem.rightBarButtonItem = newButton
             navigationItem.searchController = searchController
         }
@@ -142,9 +146,10 @@ class ChatListController: UITableViewController {
             queue: nil) { [weak self] _ in
             guard let self = self else { return }
             if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
-               self.viewModel.searchActive,
+               let viewModel = self.viewModel,
+               viewModel.searchActive,
                appDelegate.appIsInForeground() {
-                self.viewModel.updateSearchResults(for: self.searchController)
+                viewModel.updateSearchResults(for: self.searchController)
             }
         }
 
@@ -222,7 +227,7 @@ class ChatListController: UITableViewController {
     
     private func setupSubviews() {
         emptyStateLabel.addCenteredTo(parentView: view)
-        navigationItem.backButtonTitle = viewModel.isArchive ? String.localized("chat_archived_chats_title") : String.localized("pref_chats")
+        navigationItem.backButtonTitle = isArchive ? String.localized("chat_archived_chats_title") : String.localized("pref_chats")
     }
 
     @objc
@@ -240,11 +245,14 @@ class ChatListController: UITableViewController {
         tableView.rowHeight = ContactCell.cellHeight
     }
 
-    
+    private var isInitial = true
     @objc func applicationDidBecomeActive(_ notification: NSNotification) {
         if navigationController?.visibleViewController == self {
-            startTimer()
-            refreshInBg()
+            if !isInitial {
+                startTimer()
+                refreshInBg()
+                isInitial = false
+            }
         }
     }
 
@@ -259,13 +267,13 @@ class ChatListController: UITableViewController {
                 // do at least one refresh, without inital delay
                 // (refreshData() calls handleChatListUpdate() on main thread when done)
                 self?.needsAnotherBgRefresh = false
-                self?.viewModel.refreshData()
+                self?.viewModel?.refreshData()
 
                 // do subsequent refreshes with a delay of 500ms
                 while self?.needsAnotherBgRefresh != false {
                     usleep(500000)
                     self?.needsAnotherBgRefresh = false
-                    self?.viewModel.refreshData()
+                    self?.viewModel?.refreshData()
                 }
 
                 self?.inBgRefresh = false
@@ -299,7 +307,7 @@ class ChatListController: UITableViewController {
     }
     private func quitSearch(animated: Bool) {
         searchController.searchBar.text = nil
-        self.viewModel.endSearch()
+        self.viewModel?.endSearch()
         searchController.dismiss(animated: animated) {
             self.tableView.scrollToTop()
         }
@@ -309,17 +317,18 @@ class ChatListController: UITableViewController {
 
     
     override func numberOfSections(in tableView: UITableView) -> Int {
-        return viewModel.numberOfSections
+        return viewModel?.numberOfSections ?? 0
     }
 
     override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-        return viewModel.numberOfRowsIn(section: section)
+        return viewModel?.numberOfRowsIn(section: section) ?? 0
     }
 
     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
+        guard let viewModel = viewModel else {
+            return UITableViewCell()
+        }
         let cellData = viewModel.cellDataFor(section: indexPath.section, row: indexPath.row)
-
         switch cellData.type {
         case .chat(let chatData):
             let chatId = chatData.chatId
@@ -340,16 +349,23 @@ class ChatListController: UITableViewController {
             }
         case .profile:
             safe_fatalError("CellData type profile not allowed")
+        default:
+            break
         }
         safe_fatalError("Could not find/dequeue or recycle UITableViewCell.")
         return UITableViewCell()
     }
 
     override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
-        return viewModel.titleForHeaderIn(section: section)
+        return viewModel?.titleForHeaderIn(section: section)
     }
 
     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        guard let viewModel = viewModel else {
+            tableView.deselectRow(at: indexPath, animated: false)
+            return
+        }
+
         let cellData = viewModel.cellDataFor(section: indexPath.section, row: indexPath.row)
         switch cellData.type {
         case .chat(let chatData):
@@ -373,6 +389,7 @@ class ChatListController: UITableViewController {
     }
 
     override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
+        guard let viewModel = viewModel else { return [] }
 
         guard let chatId = viewModel.chatIdFor(section: indexPath.section, row: indexPath.row) else {
             return []
@@ -388,13 +405,13 @@ class ChatListController: UITableViewController {
         let archiveActionTitle: String = String.localized(archived ? "unarchive" : "archive")
 
         let archiveAction = UITableViewRowAction(style: .destructive, title: archiveActionTitle) { [weak self] _, _ in
-            self?.viewModel.archiveChatToggle(chatId: chatId)
+            self?.viewModel?.archiveChatToggle(chatId: chatId)
         }
         archiveAction.backgroundColor = UIColor.lightGray
 
         let pinned = chat.visibility==DC_CHAT_VISIBILITY_PINNED
         let pinAction = UITableViewRowAction(style: .destructive, title: String.localized(pinned ? "unpin" : "pin")) { [weak self] _, _ in
-            self?.viewModel.pinChatToggle(chatId: chat.id)
+            self?.viewModel?.pinChatToggle(chatId: chat.id)
         }
         pinAction.backgroundColor = UIColor.systemGreen
 
@@ -410,10 +427,10 @@ class ChatListController: UITableViewController {
     private func updateTitle() {
         if RelayHelper.sharedInstance.isForwarding() {
             titleView.text = String.localized("forward_to")
-            if !viewModel.isArchive {
+            if isArchive {
                 navigationItem.setLeftBarButton(cancelButton, animated: true)
             }
-        } else if viewModel.isArchive {
+        } else if isArchive {
             titleView.text = String.localized("chat_archived_chats_title")
             navigationItem.setLeftBarButton(nil, animated: true)
            
@@ -430,14 +447,14 @@ class ChatListController: UITableViewController {
     }
 
     private func handleEmptyStateLabel() {
-        if let emptySearchText = viewModel.emptySearchText {
+        if let emptySearchText = viewModel?.emptySearchText {
             let text = String.localizedStringWithFormat(
                 String.localized("search_no_result_for_x"),
                 emptySearchText
             )
             emptyStateLabel.text = text
             emptyStateLabel.isHidden = false
-        } else if viewModel.isArchive && viewModel.numberOfRowsIn(section: 0) == 0 {
+        } else if isArchive && (viewModel?.numberOfRowsIn(section: 0) ?? 0) == 0 {
             emptyStateLabel.text = String.localized("archive_empty_hint")
             emptyStateLabel.isHidden = false
         } else {
@@ -533,6 +550,7 @@ class ChatListController: UITableViewController {
     }
 
     private func deleteChat(chatId: Int, animated: Bool) {
+        guard let viewModel = viewModel else { return }
         if !animated {
             viewModel.deleteChat(chatId: chatId)
             refreshInBg()
@@ -564,8 +582,7 @@ class ChatListController: UITableViewController {
     }
 
     public func showArchive(animated: Bool) {
-        let viewModel = ChatListViewModel(dcContext: dcContext, isArchive: true)
-        let controller = ChatListController(dcContext: dcContext, viewModel: viewModel)
+        let controller = ChatListController(dcContext: dcContext, isArchive: true)
         navigationController?.pushViewController(controller, animated: animated)
     }
 
@@ -578,13 +595,13 @@ class ChatListController: UITableViewController {
 // MARK: - uisearchbardelegate
 extension ChatListController: UISearchBarDelegate {
     func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
-        viewModel.beginSearch()
+        viewModel?.beginSearch()
         return true
     }
 
     func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
         // searchBar will be set to "" by system
-        viewModel.endSearch()
+        viewModel?.endSearch()
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
            self.tableView.scrollToTop()
         }

+ 1 - 2
deltachat-ios/Coordinator/AppCoordinator.swift

@@ -41,8 +41,7 @@ class AppCoordinator {
     }
 
     private func createChatsNavigationController() -> UINavigationController {
-        let viewModel = ChatListViewModel(dcContext: dcAccounts.getSelected(), isArchive: false)
-        let root = ChatListController(dcContext: dcAccounts.getSelected(), viewModel: viewModel)
+        let root = ChatListController(dcContext: dcAccounts.getSelected(), isArchive: false)
         let nav = UINavigationController(rootViewController: root)
         let settingsImage = UIImage(named: "ic_chat")
         nav.tabBarItem = UITabBarItem(title: String.localized("pref_chats"), image: settingsImage, tag: chatsTab)