feat: update account list UI

This commit is contained in:
CMK 2021-09-14 18:21:15 +08:00
parent 66af30da2a
commit 46ebdd8059
14 changed files with 438 additions and 68 deletions

View File

@ -534,6 +534,9 @@
"show_next": "Show Next", "show_next": "Show Next",
"show_previous": "Show Previous" "show_previous": "Show Previous"
} }
},
"account_list": {
"add_account": "Add Account"
} }
} }
} }

View File

@ -425,6 +425,9 @@
DBA465952696E387002B41DB /* AppPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA465942696E387002B41DB /* AppPreference.swift */; }; DBA465952696E387002B41DB /* AppPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA465942696E387002B41DB /* AppPreference.swift */; };
DBA4B0F626C269880077136E /* Intents.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DBA4B0F926C269880077136E /* Intents.stringsdict */; }; DBA4B0F626C269880077136E /* Intents.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DBA4B0F926C269880077136E /* Intents.stringsdict */; };
DBA4B0F726C269880077136E /* Intents.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DBA4B0F926C269880077136E /* Intents.stringsdict */; }; DBA4B0F726C269880077136E /* Intents.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DBA4B0F926C269880077136E /* Intents.stringsdict */; };
DBA5A52F26F07ED800CACBAA /* PanModal in Frameworks */ = {isa = PBXBuildFile; productRef = DBA5A52E26F07ED800CACBAA /* PanModal */; };
DBA5A53126F08EF000CACBAA /* DragIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5A53026F08EF000CACBAA /* DragIndicatorView.swift */; };
DBA5A53526F0A36A00CACBAA /* AddAccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5A53426F0A36A00CACBAA /* AddAccountTableViewCell.swift */; };
DBA5E7A3263AD0A3004598BB /* PhotoLibraryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */; }; DBA5E7A3263AD0A3004598BB /* PhotoLibraryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */; };
DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A4263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift */; }; DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A4263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift */; };
DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A8263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift */; }; DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A8263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift */; };
@ -1225,6 +1228,8 @@
DBA4B0EF26C153B20077136E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; }; DBA4B0EF26C153B20077136E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
DBA4B0F526C2621D0077136E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = "<group>"; }; DBA4B0F526C2621D0077136E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = "<group>"; };
DBA4B0F826C269880077136E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Intents.stringsdict; sourceTree = "<group>"; }; DBA4B0F826C269880077136E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Intents.stringsdict; sourceTree = "<group>"; };
DBA5A53026F08EF000CACBAA /* DragIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DragIndicatorView.swift; sourceTree = "<group>"; };
DBA5A53426F0A36A00CACBAA /* AddAccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountTableViewCell.swift; sourceTree = "<group>"; };
DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryService.swift; sourceTree = "<group>"; }; DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryService.swift; sourceTree = "<group>"; };
DBA5E7A4263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuImagePreviewViewModel.swift; sourceTree = "<group>"; }; DBA5E7A4263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuImagePreviewViewModel.swift; sourceTree = "<group>"; };
DBA5E7A8263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuImagePreviewViewController.swift; sourceTree = "<group>"; }; DBA5E7A8263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuImagePreviewViewController.swift; sourceTree = "<group>"; };
@ -1387,6 +1392,7 @@
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */, 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */,
DBF7A0FC26830C33004176A2 /* FPSIndicator in Frameworks */, DBF7A0FC26830C33004176A2 /* FPSIndicator in Frameworks */,
DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */, DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */,
DBA5A52F26F07ED800CACBAA /* PanModal in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -2785,13 +2791,31 @@
DB9F58ED26EF435800E7BBE9 /* Account */ = { DB9F58ED26EF435800E7BBE9 /* Account */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DBA5A53226F08EF300CACBAA /* View */,
DBA5A53326F0932E00CACBAA /* Cell */,
DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */, DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */,
DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */, DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */,
DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */,
); );
path = Account; path = Account;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DBA5A53226F08EF300CACBAA /* View */ = {
isa = PBXGroup;
children = (
DBA5A53026F08EF000CACBAA /* DragIndicatorView.swift */,
);
path = View;
sourceTree = "<group>";
};
DBA5A53326F0932E00CACBAA /* Cell */ = {
isa = PBXGroup;
children = (
DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */,
DBA5A53426F0A36A00CACBAA /* AddAccountTableViewCell.swift */,
);
path = Cell;
sourceTree = "<group>";
};
DBA5E7A6263BD298004598BB /* ContextMenu */ = { DBA5E7A6263BD298004598BB /* ContextMenu */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -3161,6 +3185,7 @@
DB01E23226A98F0900C3965B /* MastodonMeta */, DB01E23226A98F0900C3965B /* MastodonMeta */,
DB01E23426A98F0900C3965B /* MetaTextKit */, DB01E23426A98F0900C3965B /* MetaTextKit */,
DB552D4E26BBD10C00E481F6 /* OrderedCollections */, DB552D4E26BBD10C00E481F6 /* OrderedCollections */,
DBA5A52E26F07ED800CACBAA /* PanModal */,
); );
productName = Mastodon; productName = Mastodon;
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
@ -3420,6 +3445,7 @@
DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */, DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */,
DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */, DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */,
DB552D4D26BBD10C00E481F6 /* XCRemoteSwiftPackageReference "swift-collections" */, DB552D4D26BBD10C00E481F6 /* XCRemoteSwiftPackageReference "swift-collections" */,
DBA5A52D26F07ED800CACBAA /* XCRemoteSwiftPackageReference "PanModal" */,
); );
productRefGroup = DB427DD325BAA00100D1B89D /* Products */; productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -3773,6 +3799,7 @@
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */, 2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
DB6180EF26391CA50018D199 /* MediaPreviewImageViewController.swift in Sources */, DB6180EF26391CA50018D199 /* MediaPreviewImageViewController.swift in Sources */,
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */, DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */,
DBA5A53126F08EF000CACBAA /* DragIndicatorView.swift in Sources */,
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */, DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */,
DB6180F626391D580018D199 /* MediaPreviewableViewController.swift in Sources */, DB6180F626391D580018D199 /* MediaPreviewableViewController.swift in Sources */,
2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */, 2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */,
@ -4042,6 +4069,7 @@
DB6D9F6326357848008423CD /* SettingService.swift in Sources */, DB6D9F6326357848008423CD /* SettingService.swift in Sources */,
2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */, 2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */,
2D24E12D2626FD2E00A59D4F /* NotificationViewModel+LoadOldestState.swift in Sources */, 2D24E12D2626FD2E00A59D4F /* NotificationViewModel+LoadOldestState.swift in Sources */,
DBA5A53526F0A36A00CACBAA /* AddAccountTableViewCell.swift in Sources */,
2DB72C8C262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift in Sources */, 2DB72C8C262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift in Sources */,
2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */, 2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */,
DB084B5725CBC56C00F898ED /* Status.swift in Sources */, DB084B5725CBC56C00F898ED /* Status.swift in Sources */,
@ -5845,6 +5873,14 @@
minimumVersion = 1.4.1; minimumVersion = 1.4.1;
}; };
}; };
DBA5A52D26F07ED800CACBAA /* XCRemoteSwiftPackageReference "PanModal" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/slackhq/PanModal.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.2.7;
};
};
DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */ = { DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ra1028/DifferenceKit.git"; repositoryURL = "https://github.com/ra1028/DifferenceKit.git";
@ -5957,6 +5993,11 @@
package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */; package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */;
productName = "UITextView+Placeholder"; productName = "UITextView+Placeholder";
}; };
DBA5A52E26F07ED800CACBAA /* PanModal */ = {
isa = XCSwiftPackageProductDependency;
package = DBA5A52D26F07ED800CACBAA /* XCRemoteSwiftPackageReference "PanModal" */;
productName = PanModal;
};
DBAC6482267D0B21007FE9FD /* DifferenceKit */ = { DBAC6482267D0B21007FE9FD /* DifferenceKit */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */; package = DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */;

View File

@ -7,12 +7,12 @@
<key>AppShared.xcscheme_^#shared#^_</key> <key>AppShared.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>24</integer> <integer>22</integer>
</dict> </dict>
<key>CoreDataStack.xcscheme_^#shared#^_</key> <key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>23</integer> <integer>24</integer>
</dict> </dict>
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key> <key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
<dict> <dict>
@ -97,7 +97,7 @@
<key>MastodonIntent.xcscheme_^#shared#^_</key> <key>MastodonIntent.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>25</integer> <integer>26</integer>
</dict> </dict>
<key>MastodonIntents.xcscheme_^#shared#^_</key> <key>MastodonIntents.xcscheme_^#shared#^_</key>
<dict> <dict>
@ -117,7 +117,7 @@
<key>ShareActionExtension.xcscheme_^#shared#^_</key> <key>ShareActionExtension.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>22</integer> <integer>23</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>

View File

@ -127,6 +127,15 @@
"version": "3.6.2" "version": "3.6.2"
} }
}, },
{
"package": "PanModal",
"repositoryURL": "https://github.com/slackhq/PanModal.git",
"state": {
"branch": null,
"revision": "b012aecb6b67a8e46369227f893c12544846613f",
"version": "1.2.7"
}
},
{ {
"package": "SDWebImage", "package": "SDWebImage",
"repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",

View File

@ -7,6 +7,7 @@
import UIKit import UIKit
import SafariServices import SafariServices
import CoreDataStack import CoreDataStack
import PanModal
final public class SceneCoordinator { final public class SceneCoordinator {
@ -31,6 +32,7 @@ extension SceneCoordinator {
case show // push case show // push
case showDetail // replace case showDetail // replace
case modal(animated: Bool, completion: (() -> Void)? = nil) case modal(animated: Bool, completion: (() -> Void)? = nil)
case panModal
case custom(transitioningDelegate: UIViewControllerTransitioningDelegate) case custom(transitioningDelegate: UIViewControllerTransitioningDelegate)
case customPush case customPush
case safariPresent(animated: Bool, completion: (() -> Void)? = nil) case safariPresent(animated: Bool, completion: (() -> Void)? = nil)
@ -66,6 +68,7 @@ extension SceneCoordinator {
case hashtagTimeline(viewModel: HashtagTimelineViewModel) case hashtagTimeline(viewModel: HashtagTimelineViewModel)
// profile // profile
case accountList
case profile(viewModel: ProfileViewModel) case profile(viewModel: ProfileViewModel)
case favorite(viewModel: FavoriteViewModel) case favorite(viewModel: FavoriteViewModel)
@ -87,7 +90,6 @@ extension SceneCoordinator {
case activityViewController(activityViewController: UIActivityViewController, sourceView: UIView?, barButtonItem: UIBarButtonItem?) case activityViewController(activityViewController: UIActivityViewController, sourceView: UIView?, barButtonItem: UIBarButtonItem?)
#if DEBUG #if DEBUG
case accountList
case publicTimeline case publicTimeline
#endif #endif
@ -185,6 +187,13 @@ extension SceneCoordinator {
} }
presentingViewController.present(modalNavigationController, animated: animated, completion: completion) presentingViewController.present(modalNavigationController, animated: animated, completion: completion)
case .panModal:
guard let panModalPresentable = viewController as? PanModalPresentable & UIViewController else {
assertionFailure()
return nil
}
presentingViewController.presentPanModal(panModalPresentable)
case .custom(let transitioningDelegate): case .custom(let transitioningDelegate):
viewController.modalPresentationStyle = .custom viewController.modalPresentationStyle = .custom
viewController.transitioningDelegate = transitioningDelegate viewController.transitioningDelegate = transitioningDelegate
@ -274,6 +283,9 @@ private extension SceneCoordinator {
let _viewController = HashtagTimelineViewController() let _viewController = HashtagTimelineViewController()
_viewController.viewModel = viewModel _viewController.viewModel = viewModel
viewController = _viewController viewController = _viewController
case .accountList:
let _viewController = AccountListViewController()
viewController = _viewController
case .profile(let viewModel): case .profile(let viewModel):
let _viewController = ProfileViewController() let _viewController = ProfileViewController()
_viewController.viewModel = viewModel _viewController.viewModel = viewModel
@ -322,9 +334,6 @@ private extension SceneCoordinator {
_viewController.viewModel = viewModel _viewController.viewModel = viewModel
viewController = _viewController viewController = _viewController
#if DEBUG #if DEBUG
case .accountList:
let _viewController = AccountListViewController()
viewController = _viewController
case .publicTimeline: case .publicTimeline:
let _viewController = PublicTimelineViewController() let _viewController = PublicTimelineViewController()
_viewController.viewModel = PublicTimelineViewModel(context: appContext) _viewController.viewModel = PublicTimelineViewModel(context: appContext)

View File

@ -20,6 +20,8 @@ extension MetaLabel {
case titleView case titleView
case settingTableFooter case settingTableFooter
case autoCompletion case autoCompletion
case accountListName
case accountListUsername
} }
convenience init(style: Style) { convenience init(style: Style) {
@ -74,6 +76,12 @@ extension MetaLabel {
case .autoCompletion: case .autoCompletion:
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22) font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22)
textColor = Asset.Colors.brandBlue.color textColor = Asset.Colors.brandBlue.color
case .accountListName:
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular), maximumPointSize: 22)
textColor = Asset.Colors.Label.primary.color
case .accountListUsername:
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular), maximumPointSize: 20)
textColor = Asset.Colors.Label.secondary.color
} }
self.font = font self.font = font

View File

@ -1,34 +0,0 @@
//
// AccountListTableViewCell.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-13.
//
#if DEBUG
import UIKit
final class AccountListTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension AccountListTableViewCell {
private func _init() {
}
}
#endif

View File

@ -5,11 +5,11 @@
// Created by Cirno MainasuK on 2021-9-13. // Created by Cirno MainasuK on 2021-9-13.
// //
#if DEBUG
import UIKit import UIKit
import Combine import Combine
import CoreData import CoreData
import CoreDataStack import CoreDataStack
import MastodonMeta
final class AccountListViewModel { final class AccountListViewModel {
@ -43,6 +43,7 @@ final class AccountListViewModel {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main]) snapshot.appendSections([.main])
snapshot.appendItems(authentications, toSection: .main) snapshot.appendItems(authentications, toSection: .main)
snapshot.appendItems([.addAccount], toSection: .main)
diffableDataSource.apply(snapshot) diffableDataSource.apply(snapshot)
} }
@ -58,6 +59,7 @@ extension AccountListViewModel {
enum Item: Hashable { enum Item: Hashable {
case authentication(objectID: NSManagedObjectID) case authentication(objectID: NSManagedObjectID)
case addAccount
} }
func setupDiffableDataSource( func setupDiffableDataSource(
@ -70,11 +72,37 @@ extension AccountListViewModel {
let authentication = managedObjectContext.object(with: objectID) as! MastodonAuthentication let authentication = managedObjectContext.object(with: objectID) as! MastodonAuthentication
let user = authentication.user let user = authentication.user
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AccountListTableViewCell.self), for: indexPath) as! AccountListTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AccountListTableViewCell.self), for: indexPath) as! AccountListTableViewCell
cell.textLabel?.text = user.acctWithDomain AccountListViewModel.configure(cell: cell, user: user)
return cell
case .addAccount:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AddAccountTableViewCell.self), for: indexPath) as! AddAccountTableViewCell
return cell return cell
} }
} }
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
diffableDataSource?.apply(snapshot)
}
static func configure(
cell: AccountListTableViewCell,
user: MastodonUser
) {
// avatar
cell.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: user.avatarImageURL()))
// name
do {
let content = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojiMeta)
let metaContent = try MastodonMetaContent.convert(document: content)
cell.nameLabel.configure(content: metaContent)
} catch {
assertionFailure()
cell.nameLabel.configure(content: PlaintextMetaContent(string: user.displayNameWithFallback))
}
// username
cell.usernameLabel.configure(content: PlaintextMetaContent(string: user.acctWithDomain))
} }
} }
#endif

View File

@ -5,12 +5,11 @@
// Created by Cirno MainasuK on 2021-9-13. // Created by Cirno MainasuK on 2021-9-13.
// //
#if DEBUG
import os.log import os.log
import UIKit import UIKit
import Combine import Combine
import CoreDataStack import CoreDataStack
import PanModal
final class AccountListViewController: UIViewController, NeedsDependency { final class AccountListViewController: UIViewController, NeedsDependency {
@ -32,25 +31,61 @@ final class AccountListViewController: UIViewController, NeedsDependency {
return barButtonItem return barButtonItem
}() }()
let dragIndicatorView = DragIndicatorView()
private(set) lazy var tableView: UITableView = { private(set) lazy var tableView: UITableView = {
let tableView = UITableView() let tableView = UITableView()
tableView.register(AccountListTableViewCell.self, forCellReuseIdentifier: String(describing: AccountListTableViewCell.self)) tableView.register(AccountListTableViewCell.self, forCellReuseIdentifier: String(describing: AccountListTableViewCell.self))
tableView.register(AddAccountTableViewCell.self, forCellReuseIdentifier: String(describing: AddAccountTableViewCell.self))
tableView.backgroundColor = .clear
tableView.tableFooterView = UIView()
tableView.separatorStyle = .none
return tableView return tableView
}() }()
} }
// MARK: - PanModalPresentable
extension AccountListViewController: PanModalPresentable {
var panScrollable: UIScrollView? { tableView }
var showDragIndicator: Bool { false }
var shortFormHeight: PanModalHeight {
return .contentHeight(300)
}
var longFormHeight: PanModalHeight {
return .maxHeightWithTopInset(40)
}
}
extension AccountListViewController { extension AccountListViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
view.backgroundColor = .systemBackground view.backgroundColor = ThemeService.shared.currentTheme.value.systemBackgroundColor.withAlphaComponent(0.9)
ThemeService.shared.currentTheme
.receive(on: DispatchQueue.main)
.sink { [weak self] theme in
guard let self = self else { return }
self.setupBackgroundColor(theme: theme)
}
.store(in: &disposeBag)
navigationItem.rightBarButtonItem = addBarButtonItem navigationItem.rightBarButtonItem = addBarButtonItem
dragIndicatorView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(dragIndicatorView)
NSLayoutConstraint.activate([
dragIndicatorView.topAnchor.constraint(equalTo: view.topAnchor),
dragIndicatorView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
dragIndicatorView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
dragIndicatorView.heightAnchor.constraint(equalToConstant: DragIndicatorView.height).priority(.required - 1),
])
tableView.translatesAutoresizingMaskIntoConstraints = false tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView) view.addSubview(tableView)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor), tableView.topAnchor.constraint(equalTo: dragIndicatorView.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
@ -62,6 +97,11 @@ extension AccountListViewController {
managedObjectContext: context.managedObjectContext managedObjectContext: context.managedObjectContext
) )
} }
private func setupBackgroundColor(theme: Theme) {
view.backgroundColor = theme.systemBackgroundColor.withAlphaComponent(0.9)
}
} }
extension AccountListViewController { extension AccountListViewController {
@ -79,9 +119,11 @@ extension AccountListViewController: UITableViewDelegate {
tableView.deselectRow(at: indexPath, animated: true) tableView.deselectRow(at: indexPath, animated: true)
guard let diffableDataSource = viewModel.diffableDataSource else { return } guard let diffableDataSource = viewModel.diffableDataSource else { return }
guard case let .authentication(objectID) = diffableDataSource.itemIdentifier(for: indexPath) else { return } guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
assert(Thread.isMainThread)
switch item {
case .authentication(let objectID):
assert(Thread.isMainThread)
let authentication = context.managedObjectContext.object(with: objectID) as! MastodonAuthentication let authentication = context.managedObjectContext.object(with: objectID) as! MastodonAuthentication
context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID) context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
@ -90,9 +132,9 @@ extension AccountListViewController: UITableViewDelegate {
self.coordinator.setup() self.coordinator.setup()
} }
.store(in: &disposeBag) .store(in: &disposeBag)
case .addAccount:
// TODO: add dismiss entry for welcome scene
coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
}
} }
} }
#endif

View File

@ -0,0 +1,95 @@
//
// AccountListTableViewCell.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-13.
//
import UIKit
import FLAnimatedImage
import MetaTextKit
final class CircleAvatarButton: AvatarButton {
override func layoutSubviews() {
super.layoutSubviews()
layer.masksToBounds = true
layer.cornerRadius = frame.width * 0.5
layer.borderColor = UIColor.systemFill.cgColor
layer.borderWidth = 1
}
}
final class AccountListTableViewCell: UITableViewCell {
let avatarButton = CircleAvatarButton()
let nameLabel = MetaLabel(style: .accountListName)
let usernameLabel = MetaLabel(style: .accountListUsername)
let separatorLine = UIView.separatorLine
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension AccountListTableViewCell {
private func _init() {
avatarButton.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(avatarButton)
NSLayoutConstraint.activate([
avatarButton.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
avatarButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
avatarButton.heightAnchor.constraint(equalTo: avatarButton.widthAnchor, multiplier: 1.0).priority(.required - 1),
avatarButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 30).priority(.required - 1),
])
avatarButton.setContentHuggingPriority(.defaultLow, for: .horizontal)
avatarButton.setContentHuggingPriority(.defaultLow, for: .vertical)
let labelContainerStackView = UIStackView()
labelContainerStackView.axis = .vertical
labelContainerStackView.distribution = .equalCentering
labelContainerStackView.spacing = 2
labelContainerStackView.distribution = .fillProportionally
labelContainerStackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(labelContainerStackView)
NSLayoutConstraint.activate([
labelContainerStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
labelContainerStackView.leadingAnchor.constraint(equalTo: avatarButton.trailingAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: labelContainerStackView.bottomAnchor, constant: 10),
avatarButton.heightAnchor.constraint(equalTo: labelContainerStackView.heightAnchor, multiplier: 0.8).priority(.required - 10),
labelContainerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
labelContainerStackView.addArrangedSubview(nameLabel)
labelContainerStackView.addArrangedSubview(usernameLabel)
avatarButton.isUserInteractionEnabled = false
nameLabel.isUserInteractionEnabled = false
usernameLabel.isUserInteractionEnabled = false
separatorLine.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(separatorLine)
NSLayoutConstraint.activate([
separatorLine.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)),
])
}
}
// MARK: - AvatarConfigurableView
extension AccountListTableViewCell: AvatarConfigurableView {
static var configurableAvatarImageSize: CGSize { CGSize(width: 30, height: 30) }
static var configurableAvatarImageCornerRadius: CGFloat { 0 }
var configurableAvatarImageView: FLAnimatedImageView? { avatarButton.avatarImageView }
}

View File

@ -0,0 +1,75 @@
//
// AddAccountTableViewCell.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-14.
//
import UIKit
import MetaTextKit
final class AddAccountTableViewCell: UITableViewCell {
let iconImageView: UIImageView = {
let image = UIImage(systemName: "plus.circle.fill")!
let imageView = UIImageView(image: image)
imageView.tintColor = Asset.Colors.Label.primary.color
return imageView
}()
let titleLabel: UILabel = {
let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular), maximumPointSize: 22)
label.textColor = Asset.Colors.Label.primary.color
label.text = "Add Account" // TODO: i18n
return label
}()
let usernameLabel = MetaLabel(style: .accountListUsername)
let separatorLine = UIView.separatorLine
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension AddAccountTableViewCell {
private func _init() {
iconImageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(iconImageView)
NSLayoutConstraint.activate([
iconImageView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
iconImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
iconImageView.heightAnchor.constraint(equalTo: iconImageView.widthAnchor, multiplier: 1.0).priority(.required - 1),
iconImageView.heightAnchor.constraint(greaterThanOrEqualToConstant: 30).priority(.required - 1),
])
iconImageView.setContentHuggingPriority(.defaultLow, for: .horizontal)
iconImageView.setContentHuggingPriority(.defaultLow, for: .vertical)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(titleLabel)
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 19),
titleLabel.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 19),
iconImageView.heightAnchor.constraint(equalTo: titleLabel.heightAnchor, multiplier: 1.0).priority(.required - 10),
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
separatorLine.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(separatorLine)
NSLayoutConstraint.activate([
separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)),
])
}
}

View File

@ -0,0 +1,55 @@
//
// DragIndicatorView.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-14.
//
import UIKit
final class DragIndicatorView: UIView {
static let height: CGFloat = 38
let barView = UIView()
let separatorLine = UIView.separatorLine
override init(frame: CGRect) {
super.init(frame: frame)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension DragIndicatorView {
private func _init() {
barView.backgroundColor = Asset.Colors.Label.secondary.color
barView.layer.masksToBounds = true
barView.layer.cornerRadius = 2.5
barView.translatesAutoresizingMaskIntoConstraints = false
addSubview(barView)
NSLayoutConstraint.activate([
barView.centerXAnchor.constraint(equalTo: centerXAnchor),
barView.centerYAnchor.constraint(equalTo: centerYAnchor),
barView.heightAnchor.constraint(equalToConstant: 5).priority(.required - 1),
barView.widthAnchor.constraint(equalToConstant: 36).priority(.required - 1),
])
separatorLine.translatesAutoresizingMaskIntoConstraints = false
addSubview(separatorLine)
NSLayoutConstraint.activate([
separatorLine.leadingAnchor.constraint(equalTo: leadingAnchor),
separatorLine.trailingAnchor.constraint(equalTo: trailingAnchor),
separatorLine.bottomAnchor.constraint(equalTo: bottomAnchor),
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: self)),
])
}
}

View File

@ -12,6 +12,8 @@ import SafariServices
class MainTabBarController: UITabBarController { class MainTabBarController: UITabBarController {
let logger = Logger(subsystem: "MainTabBarController", category: "UI")
var disposeBag = Set<AnyCancellable>() var disposeBag = Set<AnyCancellable>()
weak var context: AppContext! weak var context: AppContext!
@ -25,6 +27,10 @@ class MainTabBarController: UITabBarController {
case notification case notification
case me case me
var tag: Int {
return rawValue
}
var title: String { var title: String {
switch self { switch self {
case .home: return L10n.Common.Controls.Tabs.home case .home: return L10n.Common.Controls.Tabs.home
@ -121,6 +127,7 @@ extension MainTabBarController {
let tabs = Tab.allCases let tabs = Tab.allCases
let viewControllers: [UIViewController] = tabs.map { tab in let viewControllers: [UIViewController] = tabs.map { tab in
let viewController = tab.viewController(context: context, coordinator: coordinator) let viewController = tab.viewController(context: context, coordinator: coordinator)
viewController.tabBarItem.tag = tab.tag
viewController.tabBarItem.title = tab.title viewController.tabBarItem.title = tab.title
viewController.tabBarItem.image = tab.image viewController.tabBarItem.image = tab.image
viewController.tabBarItem.accessibilityLabel = tab.title viewController.tabBarItem.accessibilityLabel = tab.title
@ -204,6 +211,10 @@ extension MainTabBarController {
} }
.store(in: &disposeBag) .store(in: &disposeBag)
let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer()
tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:)))
tabBar.addGestureRecognizer(tabBarLongPressGestureRecognizer)
#if DEBUG #if DEBUG
// selectedIndex = 1 // selectedIndex = 1
#endif #endif
@ -211,6 +222,33 @@ extension MainTabBarController {
} }
extension MainTabBarController {
@objc private func tabBarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) {
guard sender.state == .began else { return }
var _tab: Tab?
let location = sender.location(in: tabBar)
for item in tabBar.items ?? [] {
guard let tab = Tab(rawValue: item.tag) else { continue }
guard let view = item.value(forKey: "view") as? UIView else { continue }
guard view.frame.contains(location) else { continue}
_tab = tab
break
}
guard let tab = _tab else { return }
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): long press \(tab.title) tab")
switch tab {
case .me:
coordinator.present(scene: .accountList, from: nil, transition: .panModal)
default:
break
}
}
}
extension MainTabBarController { extension MainTabBarController {
var notificationViewController: NotificationViewController? { var notificationViewController: NotificationViewController? {

View File

@ -70,6 +70,7 @@ The app is compatible with [toot-relay](https://github.com/DagAgren/toot-relay)
- [Nuke-FLAnimatedImage-Plugin](https://github.com/kean/Nuke-FLAnimatedImage-Plugin) - [Nuke-FLAnimatedImage-Plugin](https://github.com/kean/Nuke-FLAnimatedImage-Plugin)
- [Nuke](https://github.com/kean/Nuke) - [Nuke](https://github.com/kean/Nuke)
- [Pageboy](https://github.com/uias/Pageboy#the-basics) - [Pageboy](https://github.com/uias/Pageboy#the-basics)
- [PanModal](https://github.com/slackhq/PanModal.git)
- [SDWebImage](https://github.com/SDWebImage/SDWebImage) - [SDWebImage](https://github.com/SDWebImage/SDWebImage)
- [swift-collections](https://github.com/apple/swift-collections) - [swift-collections](https://github.com/apple/swift-collections)
- [swift-nio](https://github.com/apple/swift-nio) - [swift-nio](https://github.com/apple/swift-nio)