forked from zelo72/mastodon-ios
feat: update account list UI
This commit is contained in:
parent
66af30da2a
commit
46ebdd8059
|
@ -534,6 +534,9 @@
|
|||
"show_next": "Show Next",
|
||||
"show_previous": "Show Previous"
|
||||
}
|
||||
},
|
||||
"account_list": {
|
||||
"add_account": "Add Account"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -425,6 +425,9 @@
|
|||
DBA465952696E387002B41DB /* AppPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA465942696E387002B41DB /* AppPreference.swift */; };
|
||||
DBA4B0F626C269880077136E /* 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 */; };
|
||||
DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A4263BD28C004598BB /* ContextMenuImagePreviewViewModel.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -1387,6 +1392,7 @@
|
|||
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */,
|
||||
DBF7A0FC26830C33004176A2 /* FPSIndicator in Frameworks */,
|
||||
DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */,
|
||||
DBA5A52F26F07ED800CACBAA /* PanModal in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -2785,13 +2791,31 @@
|
|||
DB9F58ED26EF435800E7BBE9 /* Account */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DBA5A53226F08EF300CACBAA /* View */,
|
||||
DBA5A53326F0932E00CACBAA /* Cell */,
|
||||
DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */,
|
||||
DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */,
|
||||
DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */,
|
||||
);
|
||||
path = Account;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3161,6 +3185,7 @@
|
|||
DB01E23226A98F0900C3965B /* MastodonMeta */,
|
||||
DB01E23426A98F0900C3965B /* MetaTextKit */,
|
||||
DB552D4E26BBD10C00E481F6 /* OrderedCollections */,
|
||||
DBA5A52E26F07ED800CACBAA /* PanModal */,
|
||||
);
|
||||
productName = Mastodon;
|
||||
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
|
||||
|
@ -3420,6 +3445,7 @@
|
|||
DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */,
|
||||
DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */,
|
||||
DB552D4D26BBD10C00E481F6 /* XCRemoteSwiftPackageReference "swift-collections" */,
|
||||
DBA5A52D26F07ED800CACBAA /* XCRemoteSwiftPackageReference "PanModal" */,
|
||||
);
|
||||
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -3773,6 +3799,7 @@
|
|||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
||||
DB6180EF26391CA50018D199 /* MediaPreviewImageViewController.swift in Sources */,
|
||||
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */,
|
||||
DBA5A53126F08EF000CACBAA /* DragIndicatorView.swift in Sources */,
|
||||
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */,
|
||||
DB6180F626391D580018D199 /* MediaPreviewableViewController.swift in Sources */,
|
||||
2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */,
|
||||
|
@ -4042,6 +4069,7 @@
|
|||
DB6D9F6326357848008423CD /* SettingService.swift in Sources */,
|
||||
2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */,
|
||||
2D24E12D2626FD2E00A59D4F /* NotificationViewModel+LoadOldestState.swift in Sources */,
|
||||
DBA5A53526F0A36A00CACBAA /* AddAccountTableViewCell.swift in Sources */,
|
||||
2DB72C8C262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift in Sources */,
|
||||
2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */,
|
||||
DB084B5725CBC56C00F898ED /* Status.swift in Sources */,
|
||||
|
@ -5845,6 +5873,14 @@
|
|||
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" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/ra1028/DifferenceKit.git";
|
||||
|
@ -5957,6 +5993,11 @@
|
|||
package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */;
|
||||
productName = "UITextView+Placeholder";
|
||||
};
|
||||
DBA5A52E26F07ED800CACBAA /* PanModal */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DBA5A52D26F07ED800CACBAA /* XCRemoteSwiftPackageReference "PanModal" */;
|
||||
productName = PanModal;
|
||||
};
|
||||
DBAC6482267D0B21007FE9FD /* DifferenceKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */;
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
<key>AppShared.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>24</integer>
|
||||
<integer>22</integer>
|
||||
</dict>
|
||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>23</integer>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -97,7 +97,7 @@
|
|||
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>25</integer>
|
||||
<integer>26</integer>
|
||||
</dict>
|
||||
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -117,7 +117,7 @@
|
|||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>22</integer>
|
||||
<integer>23</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
|
|
@ -127,6 +127,15 @@
|
|||
"version": "3.6.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "PanModal",
|
||||
"repositoryURL": "https://github.com/slackhq/PanModal.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b012aecb6b67a8e46369227f893c12544846613f",
|
||||
"version": "1.2.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SDWebImage",
|
||||
"repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import UIKit
|
||||
import SafariServices
|
||||
import CoreDataStack
|
||||
import PanModal
|
||||
|
||||
final public class SceneCoordinator {
|
||||
|
||||
|
@ -31,6 +32,7 @@ extension SceneCoordinator {
|
|||
case show // push
|
||||
case showDetail // replace
|
||||
case modal(animated: Bool, completion: (() -> Void)? = nil)
|
||||
case panModal
|
||||
case custom(transitioningDelegate: UIViewControllerTransitioningDelegate)
|
||||
case customPush
|
||||
case safariPresent(animated: Bool, completion: (() -> Void)? = nil)
|
||||
|
@ -66,9 +68,10 @@ extension SceneCoordinator {
|
|||
case hashtagTimeline(viewModel: HashtagTimelineViewModel)
|
||||
|
||||
// profile
|
||||
case accountList
|
||||
case profile(viewModel: ProfileViewModel)
|
||||
case favorite(viewModel: FavoriteViewModel)
|
||||
|
||||
|
||||
// setting
|
||||
case settings(viewModel: SettingsViewModel)
|
||||
|
||||
|
@ -87,7 +90,6 @@ extension SceneCoordinator {
|
|||
case activityViewController(activityViewController: UIActivityViewController, sourceView: UIView?, barButtonItem: UIBarButtonItem?)
|
||||
|
||||
#if DEBUG
|
||||
case accountList
|
||||
case publicTimeline
|
||||
#endif
|
||||
|
||||
|
@ -184,7 +186,14 @@ extension SceneCoordinator {
|
|||
modalNavigationController.presentationController?.delegate = adaptivePresentationControllerDelegate
|
||||
}
|
||||
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):
|
||||
viewController.modalPresentationStyle = .custom
|
||||
viewController.transitioningDelegate = transitioningDelegate
|
||||
|
@ -274,6 +283,9 @@ private extension SceneCoordinator {
|
|||
let _viewController = HashtagTimelineViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .accountList:
|
||||
let _viewController = AccountListViewController()
|
||||
viewController = _viewController
|
||||
case .profile(let viewModel):
|
||||
let _viewController = ProfileViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
|
@ -322,9 +334,6 @@ private extension SceneCoordinator {
|
|||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
#if DEBUG
|
||||
case .accountList:
|
||||
let _viewController = AccountListViewController()
|
||||
viewController = _viewController
|
||||
case .publicTimeline:
|
||||
let _viewController = PublicTimelineViewController()
|
||||
_viewController.viewModel = PublicTimelineViewModel(context: appContext)
|
||||
|
|
|
@ -20,6 +20,8 @@ extension MetaLabel {
|
|||
case titleView
|
||||
case settingTableFooter
|
||||
case autoCompletion
|
||||
case accountListName
|
||||
case accountListUsername
|
||||
}
|
||||
|
||||
convenience init(style: Style) {
|
||||
|
@ -74,6 +76,12 @@ extension MetaLabel {
|
|||
case .autoCompletion:
|
||||
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22)
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -5,11 +5,11 @@
|
|||
// Created by Cirno MainasuK on 2021-9-13.
|
||||
//
|
||||
|
||||
#if DEBUG
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonMeta
|
||||
|
||||
final class AccountListViewModel {
|
||||
|
||||
|
@ -43,6 +43,7 @@ final class AccountListViewModel {
|
|||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.main])
|
||||
snapshot.appendItems(authentications, toSection: .main)
|
||||
snapshot.appendItems([.addAccount], toSection: .main)
|
||||
|
||||
diffableDataSource.apply(snapshot)
|
||||
}
|
||||
|
@ -58,6 +59,7 @@ extension AccountListViewModel {
|
|||
|
||||
enum Item: Hashable {
|
||||
case authentication(objectID: NSManagedObjectID)
|
||||
case addAccount
|
||||
}
|
||||
|
||||
func setupDiffableDataSource(
|
||||
|
@ -70,11 +72,37 @@ extension AccountListViewModel {
|
|||
let authentication = managedObjectContext.object(with: objectID) as! MastodonAuthentication
|
||||
let user = authentication.user
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
// Created by Cirno MainasuK on 2021-9-13.
|
||||
//
|
||||
|
||||
#if DEBUG
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import PanModal
|
||||
|
||||
final class AccountListViewController: UIViewController, NeedsDependency {
|
||||
|
||||
|
@ -32,25 +31,61 @@ final class AccountListViewController: UIViewController, NeedsDependency {
|
|||
return barButtonItem
|
||||
}()
|
||||
|
||||
let dragIndicatorView = DragIndicatorView()
|
||||
|
||||
private(set) lazy var tableView: UITableView = {
|
||||
let tableView = UITableView()
|
||||
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
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// 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 {
|
||||
override func 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
|
||||
|
||||
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
|
||||
view.addSubview(tableView)
|
||||
NSLayoutConstraint.activate([
|
||||
tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
tableView.topAnchor.constraint(equalTo: dragIndicatorView.bottomAnchor),
|
||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
|
@ -62,6 +97,11 @@ extension AccountListViewController {
|
|||
managedObjectContext: context.managedObjectContext
|
||||
)
|
||||
}
|
||||
|
||||
private func setupBackgroundColor(theme: Theme) {
|
||||
view.backgroundColor = theme.systemBackgroundColor.withAlphaComponent(0.9)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AccountListViewController {
|
||||
|
@ -79,20 +119,22 @@ extension AccountListViewController: UITableViewDelegate {
|
|||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
guard case let .authentication(objectID) = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
let authentication = context.managedObjectContext.object(with: objectID) as! MastodonAuthentication
|
||||
context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
self.coordinator.setup()
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
switch item {
|
||||
case .authentication(let objectID):
|
||||
assert(Thread.isMainThread)
|
||||
let authentication = context.managedObjectContext.object(with: objectID) as! MastodonAuthentication
|
||||
context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
self.coordinator.setup()
|
||||
}
|
||||
.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
|
||||
|
|
|
@ -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 }
|
||||
}
|
|
@ -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)),
|
||||
])
|
||||
}
|
||||
|
||||
}
|
|
@ -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)),
|
||||
])
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,8 @@ import Combine
|
|||
import SafariServices
|
||||
|
||||
class MainTabBarController: UITabBarController {
|
||||
|
||||
let logger = Logger(subsystem: "MainTabBarController", category: "UI")
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
|
@ -24,6 +26,10 @@ class MainTabBarController: UITabBarController {
|
|||
case search
|
||||
case notification
|
||||
case me
|
||||
|
||||
var tag: Int {
|
||||
return rawValue
|
||||
}
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
|
@ -121,6 +127,7 @@ extension MainTabBarController {
|
|||
let tabs = Tab.allCases
|
||||
let viewControllers: [UIViewController] = tabs.map { tab in
|
||||
let viewController = tab.viewController(context: context, coordinator: coordinator)
|
||||
viewController.tabBarItem.tag = tab.tag
|
||||
viewController.tabBarItem.title = tab.title
|
||||
viewController.tabBarItem.image = tab.image
|
||||
viewController.tabBarItem.accessibilityLabel = tab.title
|
||||
|
@ -204,6 +211,10 @@ extension MainTabBarController {
|
|||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer()
|
||||
tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:)))
|
||||
tabBar.addGestureRecognizer(tabBarLongPressGestureRecognizer)
|
||||
|
||||
#if DEBUG
|
||||
// selectedIndex = 1
|
||||
#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 {
|
||||
|
||||
var notificationViewController: NotificationViewController? {
|
||||
|
|
|
@ -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](https://github.com/kean/Nuke)
|
||||
- [Pageboy](https://github.com/uias/Pageboy#the-basics)
|
||||
- [PanModal](https://github.com/slackhq/PanModal.git)
|
||||
- [SDWebImage](https://github.com/SDWebImage/SDWebImage)
|
||||
- [swift-collections](https://github.com/apple/swift-collections)
|
||||
- [swift-nio](https://github.com/apple/swift-nio)
|
||||
|
|
Loading…
Reference in New Issue