feat: add Root scene for iPad

This commit is contained in:
CMK 2021-09-22 19:08:09 +08:00
parent 45c50e266f
commit 5b3e85b68c
11 changed files with 445 additions and 50 deletions

View File

@ -359,6 +359,9 @@
DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */; };
DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7F48442620241000796008 /* ProfileHeaderViewModel.swift */; };
DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */; };
DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */; };
DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */; };
DB852D1F26FB037800FC9D81 /* SidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */; };
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */; };
DB87D44B2609C11900D12C0D /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D44A2609C11900D12C0D /* PollOptionView.swift */; };
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */; };
@ -1138,6 +1141,9 @@
DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = "<group>"; };
DB7F48442620241000796008 /* ProfileHeaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderViewModel.swift; sourceTree = "<group>"; };
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentContainerView.swift; sourceTree = "<group>"; };
DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarViewController.swift; sourceTree = "<group>"; };
DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = "<group>"; };
DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarViewModel.swift; sourceTree = "<group>"; };
DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionCollectionViewCell.swift; sourceTree = "<group>"; };
DB87D44A2609C11900D12C0D /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; };
DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionAppendEntryCollectionViewCell.swift; sourceTree = "<group>"; };
@ -2517,6 +2523,23 @@
path = CollectionViewCell;
sourceTree = "<group>";
};
DB852D1A26FAED0100FC9D81 /* Sidebar */ = {
isa = PBXGroup;
children = (
DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */,
DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */,
);
path = Sidebar;
sourceTree = "<group>";
};
DB852D1D26FB021900FC9D81 /* Root */ = {
isa = PBXGroup;
children = (
DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */,
);
path = Root;
sourceTree = "<group>";
};
DB87D45C2609DE6600D12C0D /* TextField */ = {
isa = PBXGroup;
children = (
@ -2628,6 +2651,8 @@
children = (
2D7631A425C1532200929FB9 /* Share */,
DB6180E426391A500018D199 /* Transition */,
DB852D1D26FB021900FC9D81 /* Root */,
DB852D1A26FAED0100FC9D81 /* Sidebar */,
DB8AF54E25C13703002E6C99 /* MainTab */,
DB01409B25C40BB600F9F3CF /* Onboarding */,
DB9F58ED26EF435800E7BBE9 /* Account */,
@ -3979,6 +4004,7 @@
DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */,
2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */,
DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */,
DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */,
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */,
DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */,
@ -4010,6 +4036,7 @@
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */,
DB3667A6268AE2620027D07F /* ComposeStatusPollSection.swift in Sources */,
DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */,
DB852D1F26FB037800FC9D81 /* SidebarViewModel.swift in Sources */,
DB47229725F9EFAD00DA7F53 /* NSManagedObjectContext.swift in Sources */,
2D34D9D126148D9E0081BFC0 /* APIService+Recommend.swift in Sources */,
DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */,
@ -4047,6 +4074,7 @@
DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */,
DBCBCC092680B01B000F5B51 /* AsyncHomeTimelineViewModel+LoadMiddleState.swift in Sources */,
2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */,
DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */,
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */,
DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */,
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
@ -4711,7 +4739,7 @@
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@ -4738,7 +4766,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
@ -5003,7 +5031,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@ -5028,7 +5056,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Debug";
@ -5053,7 +5081,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Release";
@ -5078,7 +5106,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
@ -5103,7 +5131,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@ -5128,7 +5156,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Debug";
@ -5153,7 +5181,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Release";
@ -5178,7 +5206,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
@ -5269,7 +5297,7 @@
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Release";
@ -5383,7 +5411,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Release";
@ -5505,7 +5533,7 @@
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Debug";
@ -5619,7 +5647,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "ASDK - Debug";
@ -5674,7 +5702,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@ -5698,7 +5726,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;

View File

@ -7,12 +7,12 @@
<key>AppShared.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>36</integer>
<integer>39</integer>
</dict>
<key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>38</integer>
<integer>36</integer>
</dict>
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
<dict>
@ -22,7 +22,7 @@
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>14</integer>
<integer>13</integer>
</dict>
<key>Mastodon - Release.xcscheme_^#shared#^_</key>
<dict>
@ -32,17 +32,17 @@
<key>Mastodon - ar.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>11</integer>
<integer>10</integer>
</dict>
<key>Mastodon - ca.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>18</integer>
<integer>16</integer>
</dict>
<key>Mastodon - de.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>12</integer>
<integer>11</integer>
</dict>
<key>Mastodon - en.xcscheme_^#shared#^_</key>
<dict>
@ -67,12 +67,12 @@
<key>Mastodon - jp.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>16</integer>
<integer>14</integer>
</dict>
<key>Mastodon - nl.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>13</integer>
<integer>12</integer>
</dict>
<key>Mastodon - ru.xcscheme_^#shared#^_</key>
<dict>
@ -87,7 +87,7 @@
<key>Mastodon - zh_Hans.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>17</integer>
<integer>15</integer>
</dict>
<key>Mastodon.xcscheme_^#shared#^_</key>
<dict>

View File

@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
"state": {
"branch": null,
"revision": "f96b619bcb2383b43d898402283924b80e2c4bae",
"version": "5.4.3"
"revision": "d120af1e8638c7da36c8481fd61a66c0c08dc4fc",
"version": "5.4.4"
}
},
{
@ -105,8 +105,8 @@
"repositoryURL": "https://github.com/kean/Nuke.git",
"state": {
"branch": null,
"revision": "3bd3a1765bdf62d561d4c2e10e1c4fc7a010f44e",
"version": "10.3.2"
"revision": "0db18dd34998cca18e9a28bcee136f84518007a0",
"version": "10.4.1"
}
},
{
@ -159,8 +159,8 @@
"repositoryURL": "https://github.com/apple/swift-nio.git",
"state": {
"branch": null,
"revision": "8da5c5a4e6c5084c296b9f39dc54f00be146e0fa",
"version": "1.14.2"
"revision": "546610d52b19be3e19935e0880bb06b9c03f5cef",
"version": "1.14.4"
}
},
{
@ -216,15 +216,6 @@
"revision": "dad97167bf1be16aeecd109130900995dd01c515",
"version": "2.6.0"
}
},
{
"package": "UITextView+Placeholder",
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder",
"state": {
"branch": null,
"revision": "20f513ded04a040cdf5467f0891849b1763ede3b",
"version": "1.4.1"
}
}
]
},

View File

@ -111,10 +111,15 @@ extension SceneCoordinator {
extension SceneCoordinator {
// func setup() {
// let viewController = MainTabBarController(context: appContext, coordinator: self)
// sceneDelegate.window?.rootViewController = viewController
// tabBarController = viewController
// }
func setup() {
let viewController = MainTabBarController(context: appContext, coordinator: self)
sceneDelegate.window?.rootViewController = viewController
tabBarController = viewController
let splitViewController = RootSplitViewController(context: appContext, coordinator: self)
sceneDelegate.window?.rootViewController = splitViewController
}
func setupOnboardingIfNeeds(animated: Bool) {

View File

@ -111,10 +111,10 @@ extension StatusProviderFacade {
if provider.navigationController == nil {
let from = provider.presentingViewController ?? provider
provider.dismiss(animated: true) {
provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show)
provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .showDetail)
}
} else {
provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: provider, transition: .show)
provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: provider, transition: .showDetail)
}
}
}

View File

@ -95,7 +95,7 @@ extension HomeTimelineViewController {
self.view.backgroundColor = theme.secondarySystemBackgroundColor
}
.store(in: &disposeBag)
navigationItem.leftBarButtonItem = settingBarButtonItem
// navigationItem.leftBarButtonItem = settingBarButtonItem
navigationItem.titleView = titleView
titleView.delegate = self

View File

@ -97,6 +97,8 @@ class MainTabBarController: UITabBarController {
}
}
var _viewControllers: [UIViewController] = []
init(context: AppContext, coordinator: SceneCoordinator) {
self.context = context
self.coordinator = coordinator
@ -140,6 +142,7 @@ extension MainTabBarController {
viewController.tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
return viewController
}
_viewControllers = viewControllers
setViewControllers(viewControllers, animated: false)
selectedIndex = 0
@ -252,7 +255,9 @@ extension MainTabBarController {
let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer()
tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:)))
tabBar.addGestureRecognizer(tabBarLongPressGestureRecognizer)
updateTabBarDisplay()
#if DEBUG
// selectedIndex = 1
#endif
@ -263,9 +268,26 @@ extension MainTabBarController {
wizard.consume()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateTabBarDisplay()
}
}
extension MainTabBarController {
private func updateTabBarDisplay() {
switch traitCollection.horizontalSizeClass {
case .compact:
tabBar.isHidden = false
default:
tabBar.isHidden = true
}
}
}
extension MainTabBarController {
@objc private func tabBarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) {
guard sender.state == .began else { return }

View File

@ -0,0 +1,101 @@
//
// RootSplitViewController.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-22.
//
import os.log
import UIKit
final class RootSplitViewController: UISplitViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
private(set) lazy var sidebarViewController: SidebarViewController = {
let sidebarViewController = SidebarViewController()
sidebarViewController.context = context
sidebarViewController.coordinator = coordinator
sidebarViewController.viewModel = SidebarViewModel(context: context)
sidebarViewController.delegate = self
return sidebarViewController
}()
private(set) lazy var mainTabBarController = MainTabBarController(context: context, coordinator: coordinator)
init(context: AppContext, coordinator: SceneCoordinator) {
self.context = context
self.coordinator = coordinator
super.init(style: .tripleColumn)
primaryBackgroundStyle = .sidebar
preferredDisplayMode = .oneBesideSecondary
preferredSplitBehavior = .tile
if #available(iOS 14.5, *) {
displayModeButtonVisibility = .always
} else {
// Fallback on earlier versions
}
setViewController(sidebarViewController, for: .primary)
setViewController(mainTabBarController.viewControllers!.first, for: .supplementary)
setViewController(UIViewController(), for: .secondary)
setViewController(mainTabBarController, for: .compact)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
}
extension RootSplitViewController {
override func viewDidLoad() {
super.viewDidLoad()
updateBehavior(size: view.frame.size)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
updateBehavior(size: size)
}
private func updateBehavior(size: CGSize) {
// fix secondary too small on iPad mini issue
if size.width > 960 {
preferredDisplayMode = .oneBesideSecondary
preferredSplitBehavior = .tile
} else {
preferredDisplayMode = .oneBesideSecondary
preferredSplitBehavior = .displace
}
}
}
// MARK: - SidebarViewControllerDelegate
extension RootSplitViewController: SidebarViewControllerDelegate {
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) {
// FIXME: remove hard code
switch tab {
case .home:
setViewController(mainTabBarController._viewControllers[0], for: .supplementary)
case .search:
setViewController(mainTabBarController._viewControllers[1], for: .supplementary)
case .notification:
setViewController(mainTabBarController._viewControllers[2], for: .supplementary)
case .me:
setViewController(mainTabBarController._viewControllers[3], for: .supplementary)
}
}
}

View File

@ -0,0 +1,98 @@
//
// SidebarViewController.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-22.
//
import UIKit
import Combine
import CoreDataStack
protocol SidebarViewControllerDelegate: AnyObject {
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab)
}
final class SidebarViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
var viewModel: SidebarViewModel!
weak var delegate: SidebarViewControllerDelegate?
static func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout() { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
var configuration = UICollectionLayoutListConfiguration(appearance: .plain)
configuration.showsSeparators = false
let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
return section
}
return layout
}
let collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: SidebarViewController.createLayout())
collectionView.backgroundColor = .clear
return collectionView
}()
}
extension SidebarViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Title"
navigationController?.navigationBar.prefersLargeTitles = true
let barAppearance = UINavigationBarAppearance()
barAppearance.configureWithTransparentBackground()
navigationItem.standardAppearance = barAppearance
navigationItem.compactAppearance = barAppearance
navigationItem.scrollEdgeAppearance = barAppearance
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
collectionView.delegate = self
viewModel.setupDiffableDataSource(collectionView: collectionView)
}
}
// MARK: - UICollectionViewDelegate
extension SidebarViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
switch item {
case .tab(let tab):
delegate?.sidebarViewController(self, didSelectTab: tab)
case .account(let viewModel):
assert(Thread.isMainThread)
let authentication = context.managedObjectContext.object(with: viewModel.authenticationObjectID) 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:
coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
default:
// TODO:
break
}
}
}

View File

@ -0,0 +1,150 @@
//
// SidebarViewModel.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-22.
//
import UIKit
import Combine
import CoreData
import CoreDataStack
final class SidebarViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
// output
var diffableDataSource: UICollectionViewDiffableDataSource<Section, Item>!
init(context: AppContext) {
self.context = context
}
}
extension SidebarViewModel {
enum Section: Hashable, CaseIterable {
case tab
case account
}
enum Item: Hashable {
case tab(MainTabBarController.Tab)
case header(HeaderViewModel)
case account(AccountViewModel)
case addAccount
}
struct HeaderViewModel: Hashable {
let title: String
}
struct AccountViewModel: Hashable {
let authenticationObjectID: NSManagedObjectID
}
struct AddAccountViewModel: Hashable {
let id = UUID()
}
}
extension SidebarViewModel {
func setupDiffableDataSource(
collectionView: UICollectionView
) {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, MainTabBarController.Tab> { (cell, indexPath, item) in
var content = cell.defaultContentConfiguration()
content.text = item.title
content.image = item.image
cell.contentConfiguration = content
cell.accessories = []
}
let headerRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, HeaderViewModel> { (cell, indexPath, item) in
var content = cell.defaultContentConfiguration()
content.text = item.title
cell.contentConfiguration = content
cell.accessories = [.outlineDisclosure()]
}
let accountRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, AccountViewModel> { (cell, indexPath, item) in
var content = cell.defaultContentConfiguration()
let authentication = AppContext.shared.managedObjectContext.object(with: item.authenticationObjectID) as! MastodonAuthentication
content.text = authentication.user.acctWithDomain
content.image = nil
cell.contentConfiguration = content
cell.accessories = []
}
let addAccountRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, AddAccountViewModel> { (cell, indexPath, item) in
var content = cell.defaultContentConfiguration()
content.text = L10n.Scene.AccountList.addAccount
content.image = nil
cell.contentConfiguration = content
cell.accessories = []
}
diffableDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in
switch item {
case .tab(let tab):
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: tab)
case .header(let viewModel):
return collectionView.dequeueConfiguredReusableCell(using: headerRegistration, for: indexPath, item: viewModel)
case .account(let viewModel):
return collectionView.dequeueConfiguredReusableCell(using: accountRegistration, for: indexPath, item: viewModel)
case .addAccount:
return collectionView.dequeueConfiguredReusableCell(using: addAccountRegistration, for: indexPath, item: AddAccountViewModel())
}
}
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(Section.allCases)
diffableDataSource.apply(snapshot)
for section in Section.allCases {
switch section {
case .tab:
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
let items: [Item] = [
.tab(.home),
.tab(.search),
.tab(.notification),
.tab(.me),
]
sectionSnapshot.append(items, to: nil)
diffableDataSource.apply(sectionSnapshot, to: section)
case .account:
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
let headerItem = Item.header(HeaderViewModel(title: "Accounts"))
sectionSnapshot.append([headerItem], to: nil)
sectionSnapshot.append([], to: headerItem)
sectionSnapshot.append([.addAccount], to: headerItem)
sectionSnapshot.expand([headerItem])
diffableDataSource.apply(sectionSnapshot, to: section)
}
}
context.authenticationService.mastodonAuthentications
.receive(on: DispatchQueue.main)
.sink { [weak self] authentications in
guard let self = self else { return }
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
let headerItem = Item.header(HeaderViewModel(title: "Accounts"))
sectionSnapshot.append([headerItem], to: nil)
let items = authentications.map { authentication in
Item.account(AccountViewModel(authenticationObjectID: authentication.objectID))
}
sectionSnapshot.append(items, to: headerItem)
sectionSnapshot.append([.addAccount], to: headerItem)
sectionSnapshot.expand([headerItem])
self.diffableDataSource.apply(sectionSnapshot, to: .account)
}
.store(in: &disposeBag)
}
}

View File

@ -1,7 +1,7 @@
PODS:
- DateToolsSwift (5.0.0)
- FLEX (4.4.1)
- Kanna (5.2.4)
- Kanna (5.2.7)
- Keys (1.0.1)
- PINCache (3.0.3):
- PINCache/Arc-exception-safe (= 3.0.3)
@ -69,7 +69,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
DateToolsSwift: 4207ada6ad615d8dc076323d27037c94916dbfa6
FLEX: 7ca2c8cd3a435ff501ff6d2f2141e9bdc934eaab
Kanna: b9d00d7c11428308c7f95e1f1f84b8205f567a8f
Kanna: 01cfbddc127f5ff0963692f285fcbc8a9d62d234
Keys: a576f4c9c1c641ca913a959a9c62ed3f215a8de9
PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086
PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20
@ -80,4 +80,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 4db0bdf969729c5758bd923e33d9e097cb892086
COCOAPODS: 1.11.0
COCOAPODS: 1.11.2