Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions FlowCrypt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
9F5C2A8B257E6C4900DE9B4B /* ImapError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5C2A8A257E6C4900DE9B4B /* ImapError.swift */; };
9F5C2A92257E94DF00DE9B4B /* Imap+MessageOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5C2A91257E94DF00DE9B4B /* Imap+MessageOperations.swift */; };
9F5C2A99257E94E900DE9B4B /* Gmail+MessageOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5C2A98257E94E900DE9B4B /* Gmail+MessageOperations.swift */; };
9F5EF7012753DA7000CC3F88 /* UI Guidance.md in Resources */ = {isa = PBXBuildFile; fileRef = 9F5EF7002753DA7000CC3F88 /* UI Guidance.md */; };
9F5F501D26F90AE100294FA2 /* OrganisationalRulesServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5F501C26F90AE100294FA2 /* OrganisationalRulesServiceMock.swift */; };
9F5F503526F90E5F00294FA2 /* ClientConfigurationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5F503426F90E5F00294FA2 /* ClientConfigurationServiceTests.swift */; };
9F5F503C26FA6C5E00294FA2 /* CurrentUserEmailMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5F503B26FA6C5E00294FA2 /* CurrentUserEmailMock.swift */; };
Expand Down Expand Up @@ -582,6 +583,7 @@
9F5C2A8A257E6C4900DE9B4B /* ImapError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImapError.swift; sourceTree = "<group>"; };
9F5C2A91257E94DF00DE9B4B /* Imap+MessageOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+MessageOperations.swift"; sourceTree = "<group>"; };
9F5C2A98257E94E900DE9B4B /* Gmail+MessageOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Gmail+MessageOperations.swift"; sourceTree = "<group>"; };
9F5EF7002753DA7000CC3F88 /* UI Guidance.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "UI Guidance.md"; sourceTree = "<group>"; };
9F5F501C26F90AE100294FA2 /* OrganisationalRulesServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganisationalRulesServiceMock.swift; sourceTree = "<group>"; };
9F5F503426F90E5F00294FA2 /* ClientConfigurationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientConfigurationServiceTests.swift; sourceTree = "<group>"; };
9F5F503B26FA6C5E00294FA2 /* CurrentUserEmailMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentUserEmailMock.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1636,6 +1638,7 @@
51E1673C270DAFF900D27C52 /* Localizable.stringsdict */,
9F71630A234FC73E0031645E /* Localizable.strings */,
D269E025240EAD9A000495C3 /* Naming Convention.md */,
9F5EF7002753DA7000CC3F88 /* UI Guidance.md */,
);
path = Resources;
sourceTree = "<group>";
Expand Down Expand Up @@ -2296,6 +2299,7 @@
C132B9BE1EC2DBD800763715 /* LaunchScreen.storyboard in Resources */,
51E1673D270DAFF900D27C52 /* Localizable.stringsdict in Resources */,
C132B9BB1EC2DBD800763715 /* Assets.xcassets in Resources */,
9F5EF7012753DA7000CC3F88 /* UI Guidance.md in Resources */,
9F716308234FC73E0031645E /* Localizable.strings in Resources */,
D2F6D13E2434FF1400DB4065 /* providers_custom.json in Resources */,
32DCA6A3C9D59DD16B136772 /* flowcrypt-ios-prod.js.txt in Resources */,
Expand Down
133 changes: 85 additions & 48 deletions FlowCrypt/Controllers/Bootstrap/InvalidStorageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@
// Copyright © 2017-present FlowCrypt a. s. All rights reserved.
//

import UIKit
import FlowCryptUI
import AsyncDisplayKit

final class InvalidStorageViewController: TableNodeViewController {
private enum Parts: Int, CaseIterable {
case screenTitle
case title
case description
case button
}

final class InvalidStorageViewController: UIViewController {
private let error: Error
private let encryptedStorage: EncryptedStorageType
private let router: GlobalRouterType
Expand All @@ -17,7 +25,7 @@ final class InvalidStorageViewController: UIViewController {
self.error = error
self.encryptedStorage = encryptedStorage
self.router = router
super.init(nibName: nil, bundle: nil)
super.init(node: TableNode())
}

@available(*, unavailable)
Expand All @@ -27,53 +35,12 @@ final class InvalidStorageViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
title = "invalid_storage_title".localized

view.backgroundColor = .backgroundColor

let font = UIFont.systemFont(ofSize: 16)
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "invalid_storage_text".localized
label.numberOfLines = 0
label.font = font
label.textColor = .mainTextColor
view.addSubview(label)

let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isEditable = false
textView.text = error.localizedDescription
textView.font = font
textView.textColor = .mainTextColor
textView.backgroundColor = .backgroundColor
view.addSubview(textView)

let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .red
button.setTitle("invalid_storage_reset_button".localized, for: .normal)
button.setTitleColor(.white, for: .normal)
button.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .bold)
button.layer.cornerRadius = 5
view.addSubview(button)

NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
label.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),

textView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 10),
textView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
textView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
textView.bottomAnchor.constraint(equalTo: button.topAnchor, constant: -10),

button.heightAnchor.constraint(equalToConstant: 50),
button.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16),
button.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16),
button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -8)
])
node.delegate = self
node.dataSource = self
node.bounces = false
node.reloadData()
}

@objc private func handleTap() {
Expand All @@ -85,3 +52,73 @@ final class InvalidStorageViewController: UIViewController {
}
}
}

extension InvalidStorageViewController: ASTableDelegate, ASTableDataSource {
func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int {
Parts.allCases.count
}

func tableNode(_ node: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
return { [weak self] in
guard let self = self, let part = Parts(rawValue: indexPath.row) else {
return ASCellNode()
}

let insets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)
switch part {
case .screenTitle:
return SetupTitleNode(
SetupTitleNode.Input(
title: "invalid_storage_title".localized
.attributed(
.bold(18),
color: .mainTextColor,
alignment: .center
),
insets: insets,
backgroundColor: .backgroundColor
)
)
case .title:
return SetupTitleNode(
SetupTitleNode.Input(
title: "invalid_storage_text".localized
.attributed(
.regular(16),
color: .mainTextColor,
alignment: .center
),
insets: insets,
backgroundColor: .backgroundColor
)
)
case .description:
return SetupTitleNode(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like if we have very long stack trace text here, we will have very long node (big height) and button will be hidden from user. User have to scroll down to see button. In my original version button is always on screen, independent of stack trace node height.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can keep this behaviour, but should we?
Apple Human Interface Guidelines recommends to use table views for scrollable layout. Also it's more common for iOS users to scroll if content is higher than screen size. And it's quite rare to have such long localized description

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception that caused us to put this in place was indeed quite long, causing a long scroll.

I guess it's fine if the button ends up on the bottom, where user has to scroll. It's more user friendly when it's on top, but it's not too bad on a screen that should be rarely, if ever, seen.

SetupTitleNode.Input(
title: self.error.localizedDescription.attributed(
.regular(16),
color: .mainTextColor,
alignment: .center
),
insets: insets,
backgroundColor: .backgroundColor
)
)
case .button:
return ButtonCellNode(
input: ButtonCellNode.Input(
title: "invalid_storage_reset_button".localized.attributed(
.bold(16),
color: .white,
alignment: .center
),
insets: insets,
color: .red
)
) { [weak self] in
self?.handleTap()
}
}
}
}
}
1 change: 1 addition & 0 deletions FlowCrypt/Functionality/Services/GlobalRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ extension GlobalRouter: GlobalRouterType {
proceed(with: session)
}

@MainActor
private func validateEncryptedStorage(_ completion: () -> Void) {
let storage = EncryptedStorage()
do {
Expand Down
21 changes: 21 additions & 0 deletions FlowCrypt/Resources/UI Guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## FlowCrypt UI Guidance

Instead of using UIKit views and layout them we can use ASTableNode (similar to UITableView).
and build or UI in more flexible and reusable way by layouting independent ASTableNodeCells (UITableViewCell)
They will not dependent on already existed elements. So for example we can add or remove parts without touching any other elements
ASDK (Texture) can calculate UI not on Main Thread. For this we can use ASCellNodeBlock with ASCellNodes as return element

For existed nodes please check FlowCryptUI. This contains all elements which are used it the app.
Please use elements from FlowCryptUI or add new one.
Consider this as design system for the app.

Each node built in declarative way and can be build with Input (consider as view model for node)

For button - ButtonCellNode can be used with appropriate ButtonCellNode.Input
For titles - SetupTitleNode with input

Usually all inputs for node can be build with attributed text, insets and action if supported
There are a lot of usefull extensions for attributed text which uses common styling in app
(FlowCryptCommon -> String extensions)

Also app use Decorators for each View Controller focused on ui styling.