Browse Source

Merge pull request #1723 from deltachat/switch_account_layout

Switch account layout
cyBerta 2 years ago
parent
commit
e9b9429c48

+ 1 - 0
DcCore/DcCore/Helper/DcColors.swift

@@ -38,6 +38,7 @@ public struct DcColors {
     public static let systemMessageBackgroundColor = UIColor.init(hexString: "65444444")
     public static let systemMessageFontColor = UIColor.white
     public static let deaddropBackground = UIColor.themeColor(light: UIColor.init(hexString: "ebebec"), dark: UIColor.init(hexString: "1a1a1c"))
+    public static let accountSwitchBackgroundColor = UIColor.themeColor(light: UIColor.init(hexString: "65CCCCCC"), dark: UIColor.init(hexString: "65444444"))
 }
 
 public enum SystemColor {

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

@@ -111,6 +111,8 @@
 		30C7D5F128F4808C0078D24C /* MessageCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C7D5EB28F47E620078D24C /* MessageCounter.swift */; };
 		30CE137828D9C40800158DF4 /* ChatDropInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CE137728D9C40700158DF4 /* ChatDropInteraction.swift */; };
 		30DAF71C275901610073C154 /* SettingsBackgroundSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DAF71B275901610073C154 /* SettingsBackgroundSelectionController.swift */; };
+		30DDCBE928FCA1FA00465D22 /* PartialScreenPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DDCBE828FCA1F900465D22 /* PartialScreenPresentationController.swift */; };
+		30DDCBEB28FCA21800465D22 /* AccountSwitchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DDCBEA28FCA21800465D22 /* AccountSwitchViewController.swift */; };
 		30E348DF24F3F819005C93D1 /* ChatTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E348DE24F3F819005C93D1 /* ChatTableView.swift */; };
 		30E348E124F53772005C93D1 /* ImageTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E348E024F53772005C93D1 /* ImageTextCell.swift */; };
 		30E348E524F6647D005C93D1 /* FileTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E348E424F6647D005C93D1 /* FileTextCell.swift */; };
@@ -391,6 +393,8 @@
 		30C7D5EB28F47E620078D24C /* MessageCounter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCounter.swift; sourceTree = "<group>"; };
 		30CE137728D9C40700158DF4 /* ChatDropInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDropInteraction.swift; sourceTree = "<group>"; };
 		30DAF71B275901610073C154 /* SettingsBackgroundSelectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBackgroundSelectionController.swift; sourceTree = "<group>"; };
+		30DDCBE828FCA1F900465D22 /* PartialScreenPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PartialScreenPresentationController.swift; sourceTree = "<group>"; };
+		30DDCBEA28FCA21800465D22 /* AccountSwitchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountSwitchViewController.swift; sourceTree = "<group>"; };
 		30E348DE24F3F819005C93D1 /* ChatTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTableView.swift; sourceTree = "<group>"; };
 		30E348E024F53772005C93D1 /* ImageTextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTextCell.swift; sourceTree = "<group>"; };
 		30E348E424F6647D005C93D1 /* FileTextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileTextCell.swift; sourceTree = "<group>"; };
@@ -925,6 +929,8 @@
 		AE851AC0227C693B00ED86F0 /* Controller */ = {
 			isa = PBXGroup;
 			children = (
+				30DDCBEA28FCA21800465D22 /* AccountSwitchViewController.swift */,
+				30DDCBE828FCA1F900465D22 /* PartialScreenPresentationController.swift */,
 				3015634323A003BA00E9DEF4 /* AudioRecorderController.swift */,
 				AE18F28B228C17630007B1BE /* AccountSetup */,
 				AEE56D752253431E007DC082 /* AccountSetupController.swift */,
@@ -1474,6 +1480,7 @@
 				30F4E2942859213400ACA0D8 /* ChatListEditingBar.swift in Sources */,
 				AE57C0802552BBD0003CFE70 /* GalleryItem.swift in Sources */,
 				AE25F09022807AD800CDEA66 /* AvatarSelectionCell.swift in Sources */,
+				30DDCBE928FCA1FA00465D22 /* PartialScreenPresentationController.swift in Sources */,
 				30A4149724F6EFBE00EC91EB /* InfoMessageCell.swift in Sources */,
 				302B84C6239676F0001C261F /* AvatarHelper.swift in Sources */,
 				AE77838D23E32ED20093EABD /* ContactDetailViewModel.swift in Sources */,
@@ -1548,6 +1555,7 @@
 				AE6EC5242497663200A400E4 /* UIImageView+Extensions.swift in Sources */,
 				30F8817624DA97DA0023780E /* BackgroundContainer.swift in Sources */,
 				3067AA4C2666310E00525036 /* ChatInputTextView.swift in Sources */,
+				30DDCBEB28FCA21800465D22 /* AccountSwitchViewController.swift in Sources */,
 				3080A02C277DE26000E74565 /* NSConstraintLayoutSet.swift in Sources */,
 				3080A038277DE30100E74565 /* UIView+AutoLayout.swift in Sources */,
 				30B0ACFA24AB5B99004D5E29 /* SettingsEphemeralMessageController.swift in Sources */,

+ 21 - 0
deltachat-ios/Assets.xcassets/ic_checkmark.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "checkmark.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
deltachat-ios/Assets.xcassets/ic_checkmark.imageset/checkmark.png


+ 21 - 0
deltachat-ios/Assets.xcassets/ic_trash.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "trash.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
deltachat-ios/Assets.xcassets/ic_trash.imageset/trash.png


+ 371 - 0
deltachat-ios/Controller/AccountSwitchViewController.swift

@@ -0,0 +1,371 @@
+import UIKit
+import Intents
+import DcCore
+
+class AccountSwitchViewController: UITableViewController {
+
+    private let dcAccounts: DcAccounts
+    private let accountSection = 0
+    private let addSection = 1
+    private var showAccountDeletion: Bool = false
+
+    private lazy var accountIds: [Int] = {
+        return dcAccounts.getAll()
+    }()
+
+    private lazy var editButton: UIBarButtonItem = {
+        let btn = UIBarButtonItem(barButtonSystemItem: .edit,
+                                  target: self,
+                                  action: #selector(editAction))
+        return btn
+    }()
+
+    private lazy var cancelEditButton: UIBarButtonItem = {
+        let btn = UIBarButtonItem(barButtonSystemItem: .cancel,
+                                  target: self,
+                                  action: #selector(cancelEditAction))
+        return btn
+    }()
+
+    private lazy var doneButton: UIBarButtonItem = {
+        let btn = UIBarButtonItem(barButtonSystemItem: .done,
+                                  target: self,
+                                  action: #selector(doneAction))
+        return btn
+    }()
+
+    private lazy var addAccountCell: ActionCell = {
+        let cell = ActionCell()
+        cell.actionTitle = String.localized("add_account")
+        cell.backgroundColor = .clear
+        return cell
+    }()
+
+    init(dcAccounts: DcAccounts) {
+        self.dcAccounts = dcAccounts
+        showAccountDeletion = false
+        if #available(iOS 13.0, *) {
+            super.init(style: .insetGrouped)
+        } else {
+            super.init(style: .grouped)
+        }
+        setupSubviews()
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        navigationItem.setLeftBarButton(editButton, animated: false)
+        navigationItem.setRightBarButton(doneButton, animated: false)
+    }
+
+    private func setupSubviews() {
+        title = String.localized("switch_account")
+        tableView.register(AccountCell.self, forCellReuseIdentifier: AccountCell.reuseIdentifier)
+        tableView.rowHeight = AccountCell.cellHeight
+        tableView.separatorStyle = .singleLine
+        tableView.delegate = self
+    }
+
+    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+            return AccountCell.cellHeight
+    }
+
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return 2
+    }
+
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if section == accountSection {
+            return accountIds.count
+        }
+        return 1
+    }
+
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        if indexPath.section == accountSection {
+            guard let cell: AccountCell = tableView.dequeueReusableCell(withIdentifier: AccountCell.reuseIdentifier, for: indexPath) as? AccountCell else {
+                safe_fatalError("unsupported cell type")
+                return UITableViewCell()
+            }
+
+            let selectedAccountId = dcAccounts.getSelected().id
+            cell.updateCell(selectedAccount: selectedAccountId,
+                            showAccountDeletion: showAccountDeletion,
+                            dcContext: dcAccounts.get(id: accountIds[indexPath.row]))
+            return cell
+        }
+        return addAccountCell
+    }
+
+    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        if section == addSection {
+            let guide = self.view.safeAreaLayoutGuide
+            let controllerHeight = guide.layoutFrame.size.height
+            let contentHeight = CGFloat(accountIds.count + 1) * AccountCell.cellHeight + (view.safeAreaInsets.vertical / 2)
+            let diff = controllerHeight - contentHeight
+            if diff > 12 {
+                return diff
+            }
+            return 12
+        }
+        return 0
+    }
+
+    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        if section == addSection {
+            let view = UIView()
+            return view
+        }
+        return nil
+    }
+
+    func selectAccount(previousAccountId: Int, accountId: Int, cell: UITableViewCell) {
+        if previousAccountId == accountId {
+            dismiss(animated: true)
+            return
+        }
+        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
+        _ = self.dcAccounts.select(id: accountId)
+        tableView.reloadData()
+        reloadAndExit(appDelegate: appDelegate, previousAccountId: previousAccountId)
+    }
+
+    func addAccount(previousAccountId: Int) {
+        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
+        _ = self.dcAccounts.add()
+        reloadAndExit(appDelegate: appDelegate, previousAccountId: previousAccountId)
+    }
+
+    func reloadAndExit(appDelegate: AppDelegate, previousAccountId: Int) {
+        appDelegate.reloadDcContext()
+        UserDefaults.standard.setValue(previousAccountId, forKey: Constants.Keys.lastSelectedAccountKey)
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
+            self?.dismiss(animated: true)
+        }
+    }
+
+    func deleteAccount(accountId: Int) {
+        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
+
+        let prefs = UserDefaults.standard
+        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: accountId)
+            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.stopIo()
+                _ = self.dcAccounts.remove(id: accountId)
+                self.dcAccounts.startIo()
+                KeychainManager.deleteAccountSecret(id: accountId)
+                INInteraction.delete(with: "\(accountId)", 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)
+                    }
+                }
+                self.reloadAndExit(appDelegate: appDelegate, previousAccountId: 0)
+            }))
+            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)
+    }
+
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        guard let cell = tableView.cellForRow(at: indexPath) else {
+            safe_fatalError()
+            return
+        }
+
+        tableView.deselectRow(at: indexPath, animated: false)
+        let selectedAccountId = dcAccounts.getSelected().id
+
+        switch indexPath.section {
+        case accountSection:
+            let accountId = accountIds[indexPath.row]
+            if showAccountDeletion {
+                deleteAccount(accountId: accountId)
+            } else {
+                selectAccount(previousAccountId: selectedAccountId, accountId: accountId, cell: cell)
+            }
+        case addSection:
+            addAccount(previousAccountId: selectedAccountId)
+        default:
+            safe_fatalError("no such tableView section expected")
+        }
+    }
+
+    @objc private func editAction() {
+        logger.debug("edit Action")
+        title = String.localized("delete_account")
+        navigationItem.setLeftBarButton(nil, animated: true)
+        navigationItem.setRightBarButton(cancelEditButton, animated: true)
+        showAccountDeletion = true
+        tableView.reloadData()
+    }
+
+    @objc private func cancelEditAction() {
+        logger.debug("cancel Action")
+        title = String.localized("switch_account")
+        navigationItem.setLeftBarButton(editButton, animated: false)
+        navigationItem.setRightBarButton(doneButton, animated: false)
+        showAccountDeletion = false
+        tableView.reloadData()
+    }
+
+    @objc private func doneAction() {
+        logger.debug("done Action")
+        dismiss(animated: true)
+    }
+}
+
+class AccountCell: UITableViewCell {
+
+    static let reuseIdentifier = "accountCell_reuse_identifier"
+    static var cellHeight: CGFloat {
+        let textHeight = UIFont.preferredFont(forTextStyle: .body).pointSize + 24
+        if textHeight > 54 {
+            return textHeight
+        }
+        return 54
+    }
+
+    var isLargeText: Bool {
+        return UIFont.preferredFont(forTextStyle: .body).pointSize > 36
+    }
+
+    lazy var accountAvatar: InitialsBadge = {
+        let avatar = InitialsBadge(size: 37, accessibilityLabel: "")
+        return avatar
+    }()
+
+    var selectedAccount: Int?
+    var accountId: Int?
+
+    public lazy var stateIndicator: UIImageView = {
+        let view: UIImageView = UIImageView()
+        view.translatesAutoresizingMaskIntoConstraints = false
+        view.contentMode = .scaleAspectFit
+        return view
+    }()
+
+    lazy var accountName: UILabel = {
+        let label = UILabel()
+        label.translatesAutoresizingMaskIntoConstraints = false
+        label.font = UIFont.preferredFont(for: .body, weight: .bold)
+        label.textColor = DcColors.defaultTextColor
+        return label
+    }()
+
+    private lazy var backgroundContainer: BackgroundContainer = {
+        let container = BackgroundContainer()
+        container.image = UIImage(color: DcColors.accountSwitchBackgroundColor)
+        container.contentMode = .scaleToFill
+        container.clipsToBounds = true
+        container.translatesAutoresizingMaskIntoConstraints = false
+        return container
+    }()
+
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
+        setupSubviews()
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    func setupSubviews() {
+        contentView.addSubview(accountAvatar)
+        contentView.addSubview(accountName)
+        contentView.addSubview(stateIndicator)
+        let margins = contentView.layoutMarginsGuide
+        contentView.addConstraints([
+            accountAvatar.constraintCenterYTo(contentView),
+            accountAvatar.constraintAlignLeadingToAnchor(margins.leadingAnchor),
+            accountName.constraintAlignTopToAnchor(margins.topAnchor),
+            accountName.constraintToTrailingOf(accountAvatar, paddingLeading: 20),
+            accountName.constraintAlignBottomToAnchor(margins.bottomAnchor),
+            accountName.constraintAlignTrailingToAnchor(margins.trailingAnchor, paddingTrailing: 32, priority: .defaultLow),
+            stateIndicator.constraintCenterYTo(contentView),
+            stateIndicator.constraintToTrailingOf(accountName),
+            stateIndicator.constraintAlignTrailingToAnchor(margins.trailingAnchor, paddingTrailing: 0),
+            stateIndicator.constraintHeightTo(24),
+            stateIndicator.constraintWidthTo(24)
+        ])
+        stateIndicator.isHidden = true
+    }
+
+    public func updateCell(selectedAccount: Int, showAccountDeletion: Bool, dcContext: DcContext) {
+        let accountId = dcContext.id
+        self.accountId = accountId
+        self.selectedAccount = selectedAccount
+        let title = dcContext.displayname ?? dcContext.addr ?? ""
+        let contact = dcContext.getContact(id: Int(DC_CONTACT_ID_SELF))
+        accountAvatar.setColor(contact.color)
+        accountAvatar.setName(title)
+        if let image = contact.profileImage {
+            accountAvatar.setImage(image)
+        }
+
+        accountAvatar.setUnreadMessageCount(dcContext.getFreshMessages().count)
+        accountName.text = title
+        if showAccountDeletion {
+            showDeleteIndicator()
+        } else {
+            if selectedAccount == accountId {
+                showSelectedIndicator()
+            } else {
+                stateIndicator.image = nil
+                stateIndicator.isHidden = true
+            }
+        }
+    }
+
+    private func showDeleteIndicator() {
+        let image: UIImage?
+        if #available(iOS 13.0, *) {
+            image = UIImage(systemName: "trash")
+        } else {
+            image = UIImage(named: "ic_trash")
+        }
+        stateIndicator.image = image
+        stateIndicator.tintColor = .systemRed
+        stateIndicator.accessibilityLabel = String.localized("delete")
+        stateIndicator.isHidden = false
+
+    }
+
+    private func showSelectedIndicator() {
+        let image: UIImage?
+        if #available(iOS 13.0, *) {
+            image = UIImage(systemName: "checkmark")
+        } else {
+            image = UIImage(named: "ic_checkmark")
+        }
+        stateIndicator.image = image
+        stateIndicator.tintColor = .systemBlue
+        stateIndicator.accessibilityLabel = ""
+        stateIndicator.isHidden = false
+    }
+
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        accountAvatar.reset()
+        accountName.text = nil
+        accountId = -1
+        stateIndicator.image = nil
+    }
+}

+ 15 - 1
deltachat-ios/Controller/ChatListController.swift

@@ -6,6 +6,7 @@ class ChatListController: UITableViewController, AccountSwitcherHandler {
     let dcContext: DcContext
     internal let dcAccounts: DcAccounts
     var isArchive: Bool
+    private var accountSwitchTransitioningDelegate: PartialScreenModalTransitioningDelegate!
 
     private let chatCellReuseIdentifier = "chat_cell"
     private let deadDropCellReuseIdentifier = "deaddrop_cell"
@@ -574,7 +575,20 @@ class ChatListController: UITableViewController, AccountSwitcherHandler {
     }
     
     @objc private func accountButtonTapped() {
-        showSwitchAccountMenu()
+        let viewController = AccountSwitchViewController(dcAccounts: dcAccounts)
+        let accountSwitchNavigationController = UINavigationController(rootViewController: viewController)
+        if #available(iOS 15.0, *) {
+            if let sheet = accountSwitchNavigationController.sheetPresentationController {
+                sheet.detents = [.medium()]
+                sheet.preferredCornerRadius = 20
+            }
+        } else {
+            accountSwitchTransitioningDelegate = PartialScreenModalTransitioningDelegate(from: self, to: accountSwitchNavigationController)
+            accountSwitchNavigationController.modalPresentationStyle = .custom
+            accountSwitchNavigationController.transitioningDelegate = accountSwitchTransitioningDelegate
+        }
+
+        self.present(accountSwitchNavigationController, animated: true)
     }
 
     // MARK: updates

+ 112 - 0
deltachat-ios/Controller/PartialScreenPresentationController.swift

@@ -0,0 +1,112 @@
+import Foundation
+import UIKit
+
+class PartialScreenPresentationController: UIPresentationController {
+    let blurEffectView: UIVisualEffectView
+    private var direction: CGFloat = 0
+
+    lazy var tapGestureRecognizer: UITapGestureRecognizer = {
+        return UITapGestureRecognizer(target: self, action: #selector(dismiss))
+    }()
+
+    lazy var panGestureRecognizer: UIPanGestureRecognizer = {
+        return UIPanGestureRecognizer(target: self, action: #selector(didPan(pan:)))
+    }()
+
+    @objc func dismiss() {
+        self.presentedViewController.dismiss(animated: true, completion: nil)
+    }
+    
+    @objc func didPan(pan: UIPanGestureRecognizer) {
+            guard let view = pan.view, let superView = view.superview,
+                let presented = presentedView, let container = containerView else { return }
+
+            let location = pan.translation(in: superView)
+            let velocity = pan.velocity(in: superView)
+
+            let maxPresentedY = container.frame.height / 2
+            switch pan.state {
+            case .changed:
+                if location.y < 0 {
+                    break
+                }
+                presented.frame.origin.y =  maxPresentedY + location.y
+            case .ended:
+                if velocity.y > 100 && location.y  > 0 {
+                    presentedViewController.dismiss(animated: true, completion: nil)
+                } else {
+                    let offset = maxPresentedY + (maxPresentedY / 3)
+                    switch presented.frame.origin.y {
+                    case 0...offset:
+                        UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {
+                            presented.frame.origin.y = maxPresentedY
+                        })
+                    default:
+                        presentedViewController.dismiss(animated: true, completion: nil)
+                    }
+                }
+            default:
+                break
+            }
+        }
+
+    override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
+        let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.dark)
+        blurEffectView = UIVisualEffectView(effect: blurEffect)
+        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
+        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+        blurEffectView.isUserInteractionEnabled = true
+        blurEffectView.addGestureRecognizer(tapGestureRecognizer)
+        presentedViewController.view.addGestureRecognizer(panGestureRecognizer)
+    }
+
+   override var frameOfPresentedViewInContainerView: CGRect {
+       guard let containerView = self.containerView else {
+           return CGRect()
+       }
+       return CGRect(origin: CGPoint(x: 0,
+                                     y: containerView.frame.height / 2),
+                     size: CGSize(width: containerView.frame.width,
+                                  height: containerView.frame.height / 2))
+   }
+
+   override func dismissalTransitionWillBegin() {
+       self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
+           self.blurEffectView.alpha = 0
+       }, completion: { _ in
+           self.blurEffectView.removeFromSuperview()
+       })
+   }
+
+   override func presentationTransitionWillBegin() {
+       self.blurEffectView.alpha = 0
+       self.containerView?.addSubview(blurEffectView)
+       self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
+           self.blurEffectView.alpha = 1
+       })
+   }
+
+   override func containerViewWillLayoutSubviews() {
+       super.containerViewWillLayoutSubviews()
+       presentedView!.layer.masksToBounds = true
+       presentedView!.layer.cornerRadius = 10
+   }
+
+   override func containerViewDidLayoutSubviews() {
+       super.containerViewDidLayoutSubviews()
+       self.presentedView?.frame = frameOfPresentedViewInContainerView
+       blurEffectView.frame = containerView!.bounds
+   }
+}
+
+final class PartialScreenModalTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
+
+    init(from presented: UIViewController, to presenting: UIViewController) {
+        super.init()
+    }
+
+    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
+        return PartialScreenPresentationController(presentedViewController: presented, presenting: presenting)
+    }
+    
+}

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

@@ -52,7 +52,9 @@ extension AccountSwitcherHandler {
                 confirm2.addAction(UIAlertAction(title: String.localized("delete"), style: .destructive, handler: { [weak self] _ in
                     guard let self = self else { return }
                     appDelegate.locationManager.disableLocationStreamingInAllChats()
+                    self.dcAccounts.stopIo()
                     _ = self.dcAccounts.remove(id: selectedAccountId)
+                    self.dcAccounts.startIo()
                     KeychainManager.deleteAccountSecret(id: selectedAccountId)
                     INInteraction.delete(with: "\(selectedAccountId)", completion: nil)
                     if self.dcAccounts.getAll().isEmpty {