Răsfoiți Sursa

add quick switch account avatar button to chatlist header (#1687)

* add logic for account quickswitch
with avatar on top in chatlist nvaigation bar
the ui part still needs to be done

* make it look a little nicer
though it still needs some real contraint and positioning magic to look good

* make avatar a tiny bit smaller

* fix custom icon replaces back button for archived chats

* unify account switcher implementation

* adapt avatar size+font to the elsewhere used avatars; add accessibility label

* remove header

* cleanup

Co-authored-by: B. Petersen <r10s@b44t.com>
Simon Laux 2 ani în urmă
părinte
comite
48a4c821ba

+ 9 - 1
DcCore/DcCore/DC/events.swift

@@ -3,6 +3,7 @@ import UserNotifications
 
 public let dcNotificationChanged = Notification.Name(rawValue: "MrEventMsgsChanged")
 public let dcNotificationIncoming = Notification.Name(rawValue: "MrEventIncomingMsg")
+public let dcNotificationIncomingAnyAccount = Notification.Name(rawValue: "EventIncomingMsgAnyAccount")
 public let dcNotificationImexProgress = Notification.Name(rawValue: "dcNotificationImexProgress")
 public let dcNotificationConfigureProgress = Notification.Name(rawValue: "MrEventConfigureProgress")
 public let dcNotificationSecureInviterProgress = Notification.Name(rawValue: "MrEventSecureInviterProgress")
@@ -152,10 +153,17 @@ public class DcEventHandler {
             }
 
         case DC_EVENT_INCOMING_MSG:
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(name: dcNotificationIncomingAnyAccount,
+                        object: nil,
+                        userInfo: nil)
+            }
+            
             if dcContext.id != dcAccounts.getSelected().id {
                 return
             }
-            let nc = NotificationCenter.default
+            
             let userInfo = [
                 "message_id": Int(data2),
                 "chat_id": Int(data1),

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

@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		21CCE7C528E73AA500BC369E /* AccountSwitcherHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CCE7C428E73AA500BC369E /* AccountSwitcherHandler.swift */; };
 		21D6C941260623F500D0755A /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D6C9392606190600D0755A /* NotificationManager.swift */; };
 		3008CB7224F93EB900E6A617 /* AudioMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3008CB7124F93EB900E6A617 /* AudioMessageCell.swift */; };
 		3008CB7424F9436C00E6A617 /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3008CB7324F9436C00E6A617 /* AudioPlayerView.swift */; };
@@ -242,6 +243,7 @@
 
 /* Begin PBXFileReference section */
 		08432784282DC739B8EAC1E2 /* Pods-DcShare.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DcShare.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DcShare/Pods-DcShare.debug.xcconfig"; sourceTree = "<group>"; };
+		21CCE7C428E73AA500BC369E /* AccountSwitcherHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSwitcherHandler.swift; sourceTree = "<group>"; };
 		21D6C9392606190600D0755A /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
 		21EE28844E7A690D73BF5285 /* Pods-deltachat-iosTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-deltachat-iosTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.debug.xcconfig"; sourceTree = "<group>"; };
 		2F7009234DB9408201A6CDCB /* Pods_deltachat_iosTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_deltachat_iosTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1029,6 +1031,7 @@
 				AEC67A1B241CE9E4007DDBE1 /* AppStateRestorer.swift */,
 				AE8519E92272FDCA00ED86F0 /* DeviceContactsHandler.swift */,
 				AEE700242438E0E500D6992E /* ProgressAlertHandler.swift */,
+				21CCE7C428E73AA500BC369E /* AccountSwitcherHandler.swift */,
 			);
 			path = Handler;
 			sourceTree = "<group>";
@@ -1503,6 +1506,7 @@
 				30E348DF24F3F819005C93D1 /* ChatTableView.swift in Sources */,
 				30EF7308252F6A3300E2C54A /* PaddingTextView.swift in Sources */,
 				30E348E124F53772005C93D1 /* ImageTextCell.swift in Sources */,
+				21CCE7C528E73AA500BC369E /* AccountSwitcherHandler.swift in Sources */,
 				3008CB7624F95B6D00E6A617 /* AudioController.swift in Sources */,
 				3080A035277DE30100E74565 /* String+Extensions.swift in Sources */,
 				302B84CE2397F6CD001C261F /* URL+Extension.swift in Sources */,

+ 73 - 3
deltachat-ios/Controller/ChatListController.swift

@@ -1,9 +1,10 @@
 import UIKit
 import DcCore
 
-class ChatListController: UITableViewController {
+class ChatListController: UITableViewController, AccountSwitcherHandler {
     var viewModel: ChatListViewModel?
     let dcContext: DcContext
+    internal let dcAccounts: DcAccounts
     var isArchive: Bool
 
     private let chatCellReuseIdentifier = "chat_cell"
@@ -13,6 +14,7 @@ class ChatListController: UITableViewController {
     private var msgChangedObserver: NSObjectProtocol?
     private var msgsNoticedObserver: NSObjectProtocol?
     private var incomingMsgObserver: NSObjectProtocol?
+    private var incomingMsgAnyAccountObserver: NSObjectProtocol?
     private var chatModifiedObserver: NSObjectProtocol?
     private var contactsChangedObserver: NSObjectProtocol?
     private var connectivityChangedObserver: NSObjectProtocol?
@@ -69,8 +71,9 @@ class ChatListController: UITableViewController {
 
     private var editingConstraints: NSLayoutConstraintSet?
 
-    init(dcContext: DcContext, isArchive: Bool) {
+    init(dcContext: DcContext, dcAccounts: DcAccounts, isArchive: Bool) {
         self.dcContext = dcContext
+        self.dcAccounts = dcAccounts
         self.isArchive = isArchive
         super.init(style: .grouped)
         DispatchQueue.global(qos: .userInteractive).async { [weak self] in
@@ -185,6 +188,12 @@ class ChatListController: UITableViewController {
             queue: nil) { [weak self] _ in
                 self?.refreshInBg()
             }
+        incomingMsgAnyAccountObserver = nc.addObserver(
+            forName: dcNotificationIncomingAnyAccount,
+            object: nil,
+            queue: nil) { [weak self] _ in
+                self?.updateAccountButton()
+            }
         chatModifiedObserver = nc.addObserver(
             forName: dcNotificationChatModified,
             object: nil,
@@ -222,6 +231,9 @@ class ChatListController: UITableViewController {
         if let incomingMsgObserver = self.incomingMsgObserver {
             nc.removeObserver(incomingMsgObserver)
         }
+        if let incomingMsgAnyAccountObserver = self.incomingMsgAnyAccountObserver {
+            nc.removeObserver(incomingMsgAnyAccountObserver)
+        }
         if let msgsNoticedObserver = self.msgsNoticedObserver {
             nc.removeObserver(msgsNoticedObserver)
         }
@@ -522,6 +534,62 @@ class ChatListController: UITableViewController {
             view.isHidden = false
         }
     }
+    
+    lazy var accountButtonAvatar: InitialsBadge = {
+        let badge = InitialsBadge(size: 37, accessibilityLabel: String.localized("switch_account"))
+        badge.setLabelFont(UIFont.systemFont(ofSize: 14))
+        badge.accessibilityTraits = .button
+        return badge
+    }()
+    
+    private let accountButtonUnreadMessageCounter: MessageCounter = {
+        let view = MessageCounter(count: 0, size: 20)
+        view.backgroundColor = DcColors.unreadBadge
+        view.isHidden = true
+        return view
+    }()
+
+    private lazy var accountButton: UIBarButtonItem = {
+        let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 37, height: 37))
+        containerView.addSubview(accountButtonAvatar)
+        containerView.addSubview(accountButtonUnreadMessageCounter)
+        
+        let tapGestureRecognizer =  UITapGestureRecognizer(target: self, action: #selector(showSwitchAccount))
+        containerView.addGestureRecognizer(tapGestureRecognizer)
+        
+        return UIBarButtonItem(customView: containerView)
+    }()
+    
+    private func updateAccountButton() {
+        let unreadCount = getUnreadCounterOfOtherAccounts()
+        accountButtonUnreadMessageCounter.setCount(unreadCount)
+        accountButtonUnreadMessageCounter.isHidden = unreadCount == 0
+        
+        let contact = dcContext.getContact(id: Int(DC_CONTACT_ID_SELF))
+        accountButtonAvatar.setColor(contact.color)
+        accountButtonAvatar.setName(contact.displayName)
+        if let image = contact.profileImage {
+            accountButtonAvatar.setImage(image)
+        }
+    }
+    
+    private func getUnreadCounterOfOtherAccounts() -> Int {
+        var unreadCount = 0
+        let selectedAccountId = dcAccounts.getSelected().id
+        
+        for accountId in dcAccounts.getAll() {
+            if accountId == selectedAccountId {
+                continue
+            }
+            unreadCount += dcAccounts.get(id: accountId).getFreshMessages().count
+        }
+        
+        return unreadCount
+    }
+    
+    @objc private func showSwitchAccount() {
+        showSwitchAccountMenu()
+    }
 
     // MARK: updates
     private func updateTitle() {
@@ -546,6 +614,8 @@ class ChatListController: UITableViewController {
                     titleView.accessibilityHint = "\(String.localized("connectivity_connected")): \(String.localized("a11y_connectivity_hint"))"
                 }
             }
+            navigationItem.setLeftBarButton(accountButton, animated: false)
+            updateAccountButton()
         }
         titleView.isUserInteractionEnabled = !tableView.isEditing
         titleView.sizeToFit()
@@ -741,7 +811,7 @@ class ChatListController: UITableViewController {
     }
 
     public func showArchive(animated: Bool) {
-        let controller = ChatListController(dcContext: dcContext, isArchive: true)
+        let controller = ChatListController(dcContext: dcContext, dcAccounts: dcAccounts, isArchive: true)
         navigationController?.pushViewController(controller, animated: animated)
     }
 

+ 2 - 65
deltachat-ios/Controller/SettingsController.swift

@@ -3,7 +3,7 @@ import DcCore
 import DBDebugToolkit
 import Intents
 
-internal final class SettingsViewController: UITableViewController, ProgressAlertHandler {
+internal final class SettingsViewController: UITableViewController, ProgressAlertHandler, AccountSwitcherHandler {
 
     private struct SectionConfigs {
         let headerTitle: String?
@@ -33,7 +33,7 @@ internal final class SettingsViewController: UITableViewController, ProgressAler
     }
 
     private var dcContext: DcContext
-    private let dcAccounts: DcAccounts
+    internal let dcAccounts: DcAccounts
 
     private let externalPathDescr = "File Sharing/Delta Chat"
 
@@ -524,69 +524,6 @@ internal final class SettingsViewController: UITableViewController, ProgressAler
         present(error, animated: true)
     }
 
-    private func showSwitchAccountMenu() {
-        let accountIds = dcAccounts.getAll()
-        let selectedAccountId = dcAccounts.getSelected().id
-        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
-
-        let prefs = UserDefaults.standard
-        // switch account
-        let menu = UIAlertController(title: String.localized("switch_account"), message: nil, preferredStyle: .safeActionSheet)
-        for accountId in accountIds {
-            let account = dcAccounts.get(id: accountId)
-            var title = account.displaynameAndAddr
-            title = (selectedAccountId==accountId ? "✔︎ " : "") + title
-            menu.addAction(UIAlertAction(title: title, style: .default, handler: { [weak self] _ in
-                guard let self = self else { return }
-                prefs.setValue(selectedAccountId, forKey: Constants.Keys.lastSelectedAccountKey)
-                _ = self.dcAccounts.select(id: accountId)
-                appDelegate.reloadDcContext()
-            }))
-        }
-
-        // add account
-        menu.addAction(UIAlertAction(title: String.localized("add_account"), style: .default, handler: { [weak self] _ in
-            guard let self = self else { return }
-            prefs.setValue(selectedAccountId, forKey: Constants.Keys.lastSelectedAccountKey)
-            _ = self.dcAccounts.add()
-            appDelegate.reloadDcContext()
-        }))
-
-        // delete account
-        menu.addAction(UIAlertAction(title: String.localized("delete_account"), style: .destructive, handler: { [weak self] _ in
-            let confirm1 = UIAlertController(title: String.localized("delete_account_ask"), message: nil, preferredStyle: .safeActionSheet)
-            confirm1.addAction(UIAlertAction(title: String.localized("delete_account"), style: .destructive, handler: { [weak self] _ in
-                guard let self = self else { return }
-                let account = self.dcAccounts.get(id: selectedAccountId)
-                let confirm2 = UIAlertController(title: account.displaynameAndAddr,
-                    message: String.localized("forget_login_confirmation_desktop"), preferredStyle: .alert)
-                confirm2.addAction(UIAlertAction(title: String.localized("delete"), style: .destructive, handler: { [weak self] _ in
-                    guard let self = self else { return }
-                    appDelegate.locationManager.disableLocationStreamingInAllChats()
-                    _ = self.dcAccounts.remove(id: selectedAccountId)
-                    KeychainManager.deleteAccountSecret(id: selectedAccountId)
-                    INInteraction.delete(with: "\(selectedAccountId)", completion: nil)
-                    if self.dcAccounts.getAll().isEmpty {
-                        _ = self.dcAccounts.add()
-                    } else {
-                        let lastSelectedAccountId = prefs.integer(forKey: Constants.Keys.lastSelectedAccountKey)
-                        if lastSelectedAccountId != 0 {
-                            _ = self.dcAccounts.select(id: lastSelectedAccountId)
-                        }
-                    }
-                    appDelegate.reloadDcContext()
-                }))
-                confirm2.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel))
-                self.present(confirm2, animated: true, completion: nil)
-            }))
-            confirm1.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel))
-            self?.present(confirm1, animated: true, completion: nil)
-        }))
-
-        menu.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
-        present(menu, animated: true, completion: nil)
-    }
-
     private func startImex(what: Int32, passphrase: String? = nil) {
         let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
         if !documents.isEmpty {

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

@@ -41,7 +41,7 @@ class AppCoordinator {
     }
 
     private func createChatsNavigationController() -> UINavigationController {
-        let root = ChatListController(dcContext: dcAccounts.getSelected(), isArchive: false)
+        let root = ChatListController(dcContext: dcAccounts.getSelected(), dcAccounts: dcAccounts, 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)

+ 78 - 0
deltachat-ios/Handler/AccountSwitcherHandler.swift

@@ -0,0 +1,78 @@
+import Foundation
+import UIKit
+import DcCore
+import DBDebugToolkit
+import Intents
+
+protocol AccountSwitcherHandler: UIViewController {
+    var dcAccounts: DcAccounts { get }
+    func showSwitchAccountMenu()
+}
+
+extension AccountSwitcherHandler {
+    func showSwitchAccountMenu() {
+        let accountIds = dcAccounts.getAll()
+        let selectedAccountId = dcAccounts.getSelected().id
+        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
+
+        let prefs = UserDefaults.standard
+        // switch account
+        let menu = UIAlertController(title: String.localized("switch_account"), message: nil, preferredStyle: .safeActionSheet)
+        for accountId in accountIds {
+            let account = dcAccounts.get(id: accountId)
+            let newMessages = account.getFreshMessages().count
+            let messageBadge = newMessages == 0 ? "" : " [" + String(newMessages) + "]"
+            
+            var title = account.displaynameAndAddr
+            title = (selectedAccountId==accountId ? "✔︎ " : "") + title + messageBadge
+            menu.addAction(UIAlertAction(title: title, style: .default, handler: { [weak self] _ in
+                guard let self = self else { return }
+                prefs.setValue(selectedAccountId, forKey: Constants.Keys.lastSelectedAccountKey)
+                _ = self.dcAccounts.select(id: accountId)
+                appDelegate.reloadDcContext()
+            }))
+        }
+
+        // add account
+        menu.addAction(UIAlertAction(title: String.localized("add_account"), style: .default, handler: { [weak self] _ in
+            guard let self = self else { return }
+            prefs.setValue(selectedAccountId, forKey: Constants.Keys.lastSelectedAccountKey)
+            _ = self.dcAccounts.add()
+            appDelegate.reloadDcContext()
+        }))
+
+        // delete account
+        menu.addAction(UIAlertAction(title: String.localized("delete_account"), style: .destructive, handler: { [weak self] _ in
+            let confirm1 = UIAlertController(title: String.localized("delete_account_ask"), message: nil, preferredStyle: .safeActionSheet)
+            confirm1.addAction(UIAlertAction(title: String.localized("delete_account"), style: .destructive, handler: { [weak self] _ in
+                guard let self = self else { return }
+                let account = self.dcAccounts.get(id: selectedAccountId)
+                let confirm2 = UIAlertController(title: account.displaynameAndAddr,
+                    message: String.localized("forget_login_confirmation_desktop"), preferredStyle: .alert)
+                confirm2.addAction(UIAlertAction(title: String.localized("delete"), style: .destructive, handler: { [weak self] _ in
+                    guard let self = self else { return }
+                    appDelegate.locationManager.disableLocationStreamingInAllChats()
+                    _ = self.dcAccounts.remove(id: selectedAccountId)
+                    KeychainManager.deleteAccountSecret(id: selectedAccountId)
+                    INInteraction.delete(with: "\(selectedAccountId)", completion: nil)
+                    if self.dcAccounts.getAll().isEmpty {
+                        _ = self.dcAccounts.add()
+                    } else {
+                        let lastSelectedAccountId = prefs.integer(forKey: Constants.Keys.lastSelectedAccountKey)
+                        if lastSelectedAccountId != 0 {
+                            _ = self.dcAccounts.select(id: lastSelectedAccountId)
+                        }
+                    }
+                    appDelegate.reloadDcContext()
+                }))
+                confirm2.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel))
+                self.present(confirm2, animated: true, completion: nil)
+            }))
+            confirm1.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel))
+            self?.present(confirm1, animated: true, completion: nil)
+        }))
+
+        menu.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
+        present(menu, animated: true, completion: nil)
+    }
+}