forked from zelo72/mastodon-ios
feat: update for new iPad UI
This commit is contained in:
parent
b2e8eb18a0
commit
19db0afa3e
|
@ -187,6 +187,8 @@
|
|||
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; };
|
||||
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; };
|
||||
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; };
|
||||
DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03A792272A7E5700EE37C5 /* SidebarListHeaderView.swift */; };
|
||||
DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03A794272A981400EE37C5 /* ContentSplitViewController.swift */; };
|
||||
DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; };
|
||||
DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F42689B782007B274C /* ComposeTableView.swift */; };
|
||||
DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; };
|
||||
|
@ -958,6 +960,8 @@
|
|||
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationController.swift; sourceTree = "<group>"; };
|
||||
DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReplyLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = "<group>"; };
|
||||
DB03A792272A7E5700EE37C5 /* SidebarListHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListHeaderView.swift; sourceTree = "<group>"; };
|
||||
DB03A794272A981400EE37C5 /* ContentSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSplitViewController.swift; sourceTree = "<group>"; };
|
||||
DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToStatusContentTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB03F7F42689B782007B274C /* ComposeTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTableView.swift; sourceTree = "<group>"; };
|
||||
DB040ED026538E3C00BEE9D8 /* Trie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = "<group>"; };
|
||||
|
@ -2125,6 +2129,7 @@
|
|||
DBF156DE2701B17600EC00B7 /* SidebarAddAccountCollectionViewCell.swift */,
|
||||
DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */,
|
||||
DB0EF72D26FDB24F00347686 /* SidebarListContentView.swift */,
|
||||
DB03A792272A7E5700EE37C5 /* SidebarListHeaderView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2587,6 +2592,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */,
|
||||
DB03A794272A981400EE37C5 /* ContentSplitViewController.swift */,
|
||||
DB852D1A26FAED0100FC9D81 /* Sidebar */,
|
||||
DB8AF54E25C13703002E6C99 /* MainTab */,
|
||||
);
|
||||
|
@ -3873,6 +3879,7 @@
|
|||
DBA94440265D137600C537E1 /* Mastodon+Entity+Field.swift in Sources */,
|
||||
DB49A61425FF2C5600B98345 /* EmojiService.swift in Sources */,
|
||||
DBBF1DC7265251D400E5B703 /* AutoCompleteViewModel+State.swift in Sources */,
|
||||
DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */,
|
||||
DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */,
|
||||
DBCC3B9526157E6E0045B23D /* APIService+Relationship.swift in Sources */,
|
||||
2D7631B325C159F700929FB9 /* Item.swift in Sources */,
|
||||
|
@ -3935,6 +3942,7 @@
|
|||
DB71FD4625F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift in Sources */,
|
||||
DB297B1B2679FAE200704C90 /* PlaceholderImageCacheService.swift in Sources */,
|
||||
2D8FCA082637EABB00137F46 /* APIService+FollowRequest.swift in Sources */,
|
||||
DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */,
|
||||
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */,
|
||||
DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */,
|
||||
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */,
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
<key>AppShared.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>42</integer>
|
||||
<integer>35</integer>
|
||||
</dict>
|
||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>43</integer>
|
||||
<integer>38</integer>
|
||||
</dict>
|
||||
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -97,7 +97,7 @@
|
|||
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>41</integer>
|
||||
<integer>36</integer>
|
||||
</dict>
|
||||
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -117,7 +117,7 @@
|
|||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>44</integer>
|
||||
<integer>37</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
|
|
@ -90,45 +90,45 @@ final public class SceneCoordinator {
|
|||
|
||||
|
||||
// Delay in next run loop
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
// Note:
|
||||
// show (push) on phone or pad (compact)
|
||||
// showDetail in .secondary in UISplitViewController on pad (expand)
|
||||
let from: UIViewController? = {
|
||||
if let splitViewController = self.splitViewController {
|
||||
if splitViewController.mainTabBarController.topMost?.view.window != nil {
|
||||
// compact
|
||||
return splitViewController.mainTabBarController.topMost
|
||||
} else {
|
||||
// expand
|
||||
return splitViewController.viewController(for: .supplementary)
|
||||
}
|
||||
} else {
|
||||
return self.tabBarController.topMost
|
||||
}
|
||||
}()
|
||||
|
||||
// show notification related content
|
||||
guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return }
|
||||
let notificationID = String(pushNotification.notificationID)
|
||||
|
||||
switch type {
|
||||
case .follow:
|
||||
let profileViewModel = RemoteProfileViewModel(context: appContext, notificationID: notificationID)
|
||||
self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show)
|
||||
case .followRequest:
|
||||
// do nothing
|
||||
break
|
||||
case .mention, .reblog, .favourite, .poll, .status:
|
||||
let threadViewModel = RemoteThreadViewModel(context: appContext, notificationID: notificationID)
|
||||
self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show)
|
||||
case ._other:
|
||||
assertionFailure()
|
||||
break
|
||||
}
|
||||
}
|
||||
// DispatchQueue.main.async { [weak self] in
|
||||
// guard let self = self else { return }
|
||||
//
|
||||
// // Note:
|
||||
// // show (push) on phone or pad (compact)
|
||||
// // showDetail in .secondary in UISplitViewController on pad (expand)
|
||||
// let from: UIViewController? = {
|
||||
// if let splitViewController = self.splitViewController {
|
||||
// if splitViewController.mainTabBarController.topMost?.view.window != nil {
|
||||
// // compact
|
||||
// return splitViewController.mainTabBarController.topMost
|
||||
// } else {
|
||||
// // expand
|
||||
// return splitViewController.viewController(for: .supplementary)
|
||||
// }
|
||||
// } else {
|
||||
// return self.tabBarController.topMost
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// // show notification related content
|
||||
// guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return }
|
||||
// let notificationID = String(pushNotification.notificationID)
|
||||
//
|
||||
// switch type {
|
||||
// case .follow:
|
||||
// let profileViewModel = RemoteProfileViewModel(context: appContext, notificationID: notificationID)
|
||||
// self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show)
|
||||
// case .followRequest:
|
||||
// // do nothing
|
||||
// break
|
||||
// case .mention, .reblog, .favourite, .poll, .status:
|
||||
// let threadViewModel = RemoteThreadViewModel(context: appContext, notificationID: notificationID)
|
||||
// self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show)
|
||||
// case ._other:
|
||||
// assertionFailure()
|
||||
// break
|
||||
// }
|
||||
// } // end DispatchQueue.main.async
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ extension SceneCoordinator {
|
|||
default:
|
||||
let splitViewController = RootSplitViewController(context: appContext, coordinator: self)
|
||||
self.splitViewController = splitViewController
|
||||
self.tabBarController = splitViewController.mainTabBarController
|
||||
self.tabBarController = splitViewController.contentSplitViewController.mainTabBarController
|
||||
sceneDelegate.window?.rootViewController = splitViewController
|
||||
}
|
||||
}
|
||||
|
@ -282,18 +282,19 @@ extension SceneCoordinator {
|
|||
|
||||
switch transition {
|
||||
case .show:
|
||||
if let splitViewController = splitViewController, !splitViewController.isCollapsed,
|
||||
let supplementaryViewController = splitViewController.viewController(for: .supplementary) as? UINavigationController,
|
||||
(supplementaryViewController === presentingViewController || supplementaryViewController.viewControllers.contains(presentingViewController)) ||
|
||||
(presentingViewController is UserTimelineViewController && presentingViewController.view.isDescendant(of: supplementaryViewController.view))
|
||||
{
|
||||
fallthrough
|
||||
} else {
|
||||
if secondaryStackHashValues.contains(presentingViewController.hashValue) {
|
||||
secondaryStackHashValues.insert(viewController.hashValue)
|
||||
}
|
||||
// if let splitViewController = splitViewController, !splitViewController.isCollapsed,
|
||||
// let supplementaryViewController = splitViewController.viewController(for: .supplementary) as? UINavigationController,
|
||||
// (supplementaryViewController === presentingViewController || supplementaryViewController.viewControllers.contains(presentingViewController)) ||
|
||||
// (presentingViewController is UserTimelineViewController && presentingViewController.view.isDescendant(of: supplementaryViewController.view))
|
||||
// {
|
||||
// fallthrough
|
||||
// } else {
|
||||
// if secondaryStackHashValues.contains(presentingViewController.hashValue) {
|
||||
// secondaryStackHashValues.insert(viewController.hashValue)
|
||||
// }
|
||||
// presentingViewController.show(viewController, sender: sender)
|
||||
// }
|
||||
presentingViewController.show(viewController, sender: sender)
|
||||
}
|
||||
case .showDetail:
|
||||
secondaryStackHashValues.insert(viewController.hashValue)
|
||||
let navigationController = AdaptiveStatusBarStyleNavigationController(rootViewController: viewController)
|
||||
|
|
|
@ -96,6 +96,9 @@ internal enum Asset {
|
|||
internal static let usernameGray = ColorAsset(name: "Scene/Profile/Banner/username.gray")
|
||||
}
|
||||
}
|
||||
internal enum Sidebar {
|
||||
internal static let logo = ImageAsset(name: "Scene/Sidebar/logo")
|
||||
}
|
||||
internal enum Welcome {
|
||||
internal enum Illustration {
|
||||
internal static let backgroundCyan = ColorAsset(name: "Scene/Welcome/illustration/background.cyan")
|
||||
|
@ -160,23 +163,6 @@ internal enum Asset {
|
|||
internal static let tabBarItemInactiveIconColor = ColorAsset(name: "Theme/system/tab.bar.item.inactive.icon.color")
|
||||
}
|
||||
}
|
||||
internal enum Deprecated {
|
||||
internal enum Background {
|
||||
internal static let danger = ColorAsset(name: "_Deprecated/Background/danger")
|
||||
internal static let onboardingBackground = ColorAsset(name: "_Deprecated/Background/onboarding.background")
|
||||
internal static let secondaryGroupedSystemBackground = ColorAsset(name: "_Deprecated/Background/secondary.grouped.system.background")
|
||||
internal static let secondarySystemBackground = ColorAsset(name: "_Deprecated/Background/secondary.system.background")
|
||||
internal static let systemBackground = ColorAsset(name: "_Deprecated/Background/system.background")
|
||||
internal static let systemElevatedBackground = ColorAsset(name: "_Deprecated/Background/system.elevated.background")
|
||||
internal static let systemGroupedBackground = ColorAsset(name: "_Deprecated/Background/system.grouped.background")
|
||||
internal static let tertiarySystemBackground = ColorAsset(name: "_Deprecated/Background/tertiary.system.background")
|
||||
internal static let tertiarySystemGroupedBackground = ColorAsset(name: "_Deprecated/Background/tertiary.system.grouped.background")
|
||||
}
|
||||
internal enum Compose {
|
||||
internal static let background = ColorAsset(name: "_Deprecated/Compose/background")
|
||||
internal static let toolbarBackground = ColorAsset(name: "_Deprecated/Compose/toolbar.background")
|
||||
}
|
||||
}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
|
||||
|
||||
|
|
15
Mastodon/Resources/Assets.xcassets/Scene/Sidebar/logo.imageset/Contents.json
vendored
Normal file
15
Mastodon/Resources/Assets.xcassets/Scene/Sidebar/logo.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "logo.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 -0.103455 cm
|
||||
0.168627 0.564706 0.850980 scn
|
||||
27.796436 10.091343 m
|
||||
33.035133 10.719734 37.596470 13.962151 38.169762 16.924883 c
|
||||
39.073063 21.591980 38.998501 28.314186 38.998501 28.314186 c
|
||||
38.998501 37.425270 33.056084 40.095867 33.056084 40.095867 c
|
||||
30.059872 41.478233 24.914881 42.059555 19.569633 42.103455 c
|
||||
19.438305 42.103455 l
|
||||
14.093056 42.059555 8.951445 41.478233 5.955006 40.095867 c
|
||||
5.955006 40.095867 0.012361 37.425270 0.012361 28.314186 c
|
||||
0.012361 27.761837 0.009520 27.180878 0.006561 26.576080 c
|
||||
-0.001656 24.896429 -0.010772 23.032921 0.037591 21.087820 c
|
||||
0.253392 12.177679 1.663759 3.396290 9.864657 1.215820 c
|
||||
13.645910 0.210445 16.892391 0.000000 19.507011 0.144371 c
|
||||
24.248556 0.408443 26.910255 1.844212 26.910255 1.844212 c
|
||||
26.753922 5.300014 l
|
||||
26.753922 5.300014 23.365528 4.226753 19.560173 4.357544 c
|
||||
15.789957 4.487431 11.809797 4.765984 11.200012 9.415886 c
|
||||
11.143697 9.824329 11.115539 10.261055 11.115539 10.719732 c
|
||||
11.115539 10.719732 14.816599 9.810978 19.507011 9.595104 c
|
||||
22.375050 9.462955 25.064680 9.763912 27.796436 10.091343 c
|
||||
h
|
||||
31.989010 16.575367 m
|
||||
31.989010 27.607372 l
|
||||
31.989010 29.862061 31.417519 31.653776 30.269808 32.979347 c
|
||||
29.085829 34.304916 27.535576 34.984444 25.611385 34.984444 c
|
||||
23.384670 34.984444 21.698582 34.124794 20.583984 32.405266 c
|
||||
19.500023 30.580288 l
|
||||
18.416286 32.405266 l
|
||||
17.301464 34.124794 15.615376 34.984444 13.388884 34.984444 c
|
||||
11.464469 34.984444 9.914215 34.304916 8.730462 32.979347 c
|
||||
7.582527 31.653776 7.011036 29.862061 7.011036 27.607372 c
|
||||
7.011036 16.575367 l
|
||||
11.361976 16.575367 l
|
||||
11.361976 27.283108 l
|
||||
11.361976 29.540287 12.307401 30.685961 14.198477 30.685961 c
|
||||
16.289360 30.685961 17.337505 29.326900 17.337505 26.639557 c
|
||||
17.337505 20.778585 l
|
||||
21.662764 20.778585 l
|
||||
21.662764 26.639557 l
|
||||
21.662764 29.326900 22.710684 30.685961 24.801567 30.685961 c
|
||||
26.692642 30.685961 27.638069 29.540287 27.638069 27.283108 c
|
||||
27.638069 16.575367 l
|
||||
31.989010 16.575367 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2035
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 39.000000 42.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002125 00000 n
|
||||
0000002148 00000 n
|
||||
0000002321 00000 n
|
||||
0000002395 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2454
|
||||
%%EOF
|
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "46",
|
||||
"green" : "44",
|
||||
"red" : "44"
|
||||
"blue" : "0x2E",
|
||||
"green" : "0x2C",
|
||||
"red" : "0x2C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.263",
|
||||
"green" : "0.208",
|
||||
"red" : "0.192"
|
||||
"blue" : "0x2E",
|
||||
"green" : "0x2C",
|
||||
"red" : "0x2C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.353",
|
||||
"green" : "0.251",
|
||||
"red" : "0.875"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.910",
|
||||
"green" : "0.882",
|
||||
"red" : "0.851"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.996",
|
||||
"green" : "1.000",
|
||||
"red" : "0.996"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.263",
|
||||
"green" : "0.208",
|
||||
"red" : "0.192"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.910",
|
||||
"green" : "0.882",
|
||||
"red" : "0.851"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.133",
|
||||
"green" : "0.106",
|
||||
"red" : "0.098"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.996",
|
||||
"green" : "1.000",
|
||||
"red" : "0.996"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.216",
|
||||
"green" : "0.173",
|
||||
"red" : "0.157"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.216",
|
||||
"green" : "0.173",
|
||||
"red" : "0.157"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.910",
|
||||
"green" : "0.882",
|
||||
"red" : "0.851"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.133",
|
||||
"green" : "0.106",
|
||||
"red" : "0.098"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.996",
|
||||
"green" : "1.000",
|
||||
"red" : "0.996"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.216",
|
||||
"green" : "0.173",
|
||||
"red" : "0.157"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.910",
|
||||
"green" : "0.882",
|
||||
"red" : "0.851"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.263",
|
||||
"green" : "0.208",
|
||||
"red" : "0.192"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.216",
|
||||
"green" : "0.173",
|
||||
"red" : "0.157"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.871",
|
||||
"green" : "0.847",
|
||||
"red" : "0.839"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.920",
|
||||
"blue" : "0.125",
|
||||
"green" : "0.125",
|
||||
"red" : "0.125"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
|
@ -114,6 +114,24 @@ extension HomeTimelineViewController {
|
|||
#endif
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
#if DEBUG
|
||||
// long press to trigger debug menu
|
||||
settingBarButtonItem.menu = debugMenu
|
||||
#else
|
||||
settingBarButtonItem.target = self
|
||||
settingBarButtonItem.action = #selector(HomeTimelineViewController.settingBarButtonItemPressed(_:))
|
||||
#endif
|
||||
|
||||
viewModel.displayComposeBarButtonItem
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] displayComposeBarButtonItem in
|
||||
guard let self = self else { return }
|
||||
self.navigationItem.rightBarButtonItem = displayComposeBarButtonItem ? self.composeBarButtonItem : nil
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
composeBarButtonItem.target = self
|
||||
composeBarButtonItem.action = #selector(HomeTimelineViewController.composeBarButtonItemPressed(_:))
|
||||
|
||||
navigationItem.titleView = titleView
|
||||
titleView.delegate = self
|
||||
|
||||
|
@ -126,18 +144,6 @@ extension HomeTimelineViewController {
|
|||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
#if DEBUG
|
||||
// long press to trigger debug menu
|
||||
settingBarButtonItem.menu = debugMenu
|
||||
#else
|
||||
settingBarButtonItem.target = self
|
||||
settingBarButtonItem.action = #selector(HomeTimelineViewController.settingBarButtonItemPressed(_:))
|
||||
#endif
|
||||
|
||||
navigationItem.rightBarButtonItem = composeBarButtonItem
|
||||
composeBarButtonItem.target = self
|
||||
composeBarButtonItem.action = #selector(HomeTimelineViewController.composeBarButtonItemPressed(_:))
|
||||
|
||||
tableView.refreshControl = refreshControl
|
||||
refreshControl.addTarget(self, action: #selector(HomeTimelineViewController.refreshControlValueChanged(_:)), for: .valueChanged)
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ final class HomeTimelineViewModel: NSObject {
|
|||
let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel
|
||||
let lastAutomaticFetchTimestamp = CurrentValueSubject<Date?, Never>(nil)
|
||||
let scrollPositionRecord = CurrentValueSubject<ScrollPositionRecord?, Never>(nil)
|
||||
let displaySettingBarButtonItem = CurrentValueSubject<Bool, Never>(true)
|
||||
let displayComposeBarButtonItem = CurrentValueSubject<Bool, Never>(true)
|
||||
|
||||
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
|
||||
weak var tableView: UITableView?
|
||||
|
@ -70,7 +72,6 @@ final class HomeTimelineViewModel: NSObject {
|
|||
let loadMiddleSateMachineList = CurrentValueSubject<[NSManagedObjectID: GKStateMachine], Never>([:]) // TimelineIndex.objectID : middle loading state machine
|
||||
var diffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>?
|
||||
var cellFrameCache = NSCache<NSNumber, NSValue>()
|
||||
let displaySettingBarButtonItem = CurrentValueSubject<Bool, Never>(true)
|
||||
|
||||
init(context: AppContext) {
|
||||
self.context = context
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// ContentSplitViewController.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-10-28.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
|
||||
final class ContentSplitViewController: UIViewController, NeedsDependency {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
static let sidebarWidth: CGFloat = 89
|
||||
|
||||
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
|
||||
}()
|
||||
|
||||
@Published var currentSupplementaryTab: MainTabBarController.Tab = .home
|
||||
private(set) lazy var mainTabBarController: MainTabBarController = {
|
||||
let mainTabBarController = MainTabBarController(context: context, coordinator: coordinator)
|
||||
return mainTabBarController
|
||||
}()
|
||||
|
||||
deinit {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ContentSplitViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
navigationController?.setNavigationBarHidden(true, animated: false)
|
||||
|
||||
addChild(sidebarViewController)
|
||||
sidebarViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(sidebarViewController.view)
|
||||
sidebarViewController.didMove(toParent: self)
|
||||
NSLayoutConstraint.activate([
|
||||
sidebarViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
sidebarViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
sidebarViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
sidebarViewController.view.widthAnchor.constraint(equalToConstant: ContentSplitViewController.sidebarWidth),
|
||||
])
|
||||
|
||||
addChild(mainTabBarController)
|
||||
mainTabBarController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(mainTabBarController.view)
|
||||
sidebarViewController.didMove(toParent: self)
|
||||
NSLayoutConstraint.activate([
|
||||
mainTabBarController.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
mainTabBarController.view.leadingAnchor.constraint(equalTo: sidebarViewController.view.trailingAnchor),
|
||||
mainTabBarController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
mainTabBarController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
||||
$currentSupplementaryTab
|
||||
.removeDuplicates()
|
||||
.sink(receiveValue: { [weak self] tab in
|
||||
guard let self = self else { return }
|
||||
self.mainTabBarController.selectedIndex = tab.rawValue
|
||||
self.mainTabBarController.currentTab.value = tab
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SidebarViewControllerDelegate
|
||||
extension ContentSplitViewController: SidebarViewControllerDelegate {
|
||||
|
||||
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) {
|
||||
guard let _ = MainTabBarController.Tab.allCases.firstIndex(of: tab) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
currentSupplementaryTab = tab
|
||||
}
|
||||
}
|
|
@ -70,6 +70,9 @@ extension MainTabBarController.Wizard {
|
|||
|
||||
func setup(in view: UIView) {
|
||||
assert(delegate != nil, "need set delegate before use")
|
||||
|
||||
guard !items.isEmpty else { return }
|
||||
|
||||
backgroundView.frame = view.bounds
|
||||
backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(backgroundView)
|
||||
|
|
|
@ -14,57 +14,44 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency {
|
|||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
static let sidebarWidth: CGFloat = 89
|
||||
|
||||
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 contentSplitViewController: ContentSplitViewController = {
|
||||
let contentSplitViewController = ContentSplitViewController()
|
||||
contentSplitViewController.context = context
|
||||
contentSplitViewController.coordinator = coordinator
|
||||
return contentSplitViewController
|
||||
}()
|
||||
|
||||
var currentSupplementaryTab: MainTabBarController.Tab = .home
|
||||
private(set) lazy var supplementaryViewControllers: [UIViewController] = {
|
||||
let viewControllers = MainTabBarController.Tab.allCases.map { tab in
|
||||
tab.viewController(context: context, coordinator: coordinator)
|
||||
}
|
||||
for viewController in viewControllers {
|
||||
guard let navigationController = viewController as? UINavigationController else {
|
||||
assertionFailure()
|
||||
continue
|
||||
}
|
||||
if let homeViewController = navigationController.topViewController as? HomeTimelineViewController {
|
||||
homeViewController.viewModel.displaySettingBarButtonItem.value = false
|
||||
}
|
||||
}
|
||||
return viewControllers
|
||||
}()
|
||||
|
||||
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)
|
||||
super.init(style: .doubleColumn)
|
||||
|
||||
primaryEdge = .trailing
|
||||
primaryBackgroundStyle = .sidebar
|
||||
preferredDisplayMode = .oneBesideSecondary
|
||||
preferredDisplayMode = .twoBesideSecondary
|
||||
preferredSplitBehavior = .tile
|
||||
delegate = self
|
||||
|
||||
// disable edge swipe gesture
|
||||
presentsWithGesture = false
|
||||
|
||||
if #available(iOS 14.5, *) {
|
||||
displayModeButtonVisibility = .always
|
||||
displayModeButtonVisibility = .never
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
|
||||
setViewController(sidebarViewController, for: .primary)
|
||||
setViewController(supplementaryViewControllers[0], for: .supplementary)
|
||||
setViewController(SecondaryPlaceholderViewController(), for: .secondary)
|
||||
setViewController(mainTabBarController, for: .compact)
|
||||
setViewController(UIViewController(), for: .primary)
|
||||
setViewController(contentSplitViewController, for: .secondary)
|
||||
|
||||
contentSplitViewController.sidebarViewController.view.layer.zPosition = 100
|
||||
contentSplitViewController.mainTabBarController.view.layer.zPosition = 90
|
||||
view.layer.zPosition = 80
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -83,16 +70,11 @@ extension RootSplitViewController {
|
|||
super.viewDidLoad()
|
||||
|
||||
updateBehavior(size: view.frame.size)
|
||||
|
||||
mainTabBarController.currentTab
|
||||
contentSplitViewController.$currentSupplementaryTab
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] tab in
|
||||
.sink { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
guard tab != self.currentSupplementaryTab else { return }
|
||||
guard let index = MainTabBarController.Tab.allCases.firstIndex(of: tab) else { return }
|
||||
self.currentSupplementaryTab = tab
|
||||
self.setViewController(self.supplementaryViewControllers[index], for: .supplementary)
|
||||
|
||||
self.updateBehavior(size: self.view.frame.size)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
@ -100,54 +82,23 @@ extension RootSplitViewController {
|
|||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
updateBehavior(size: size)
|
||||
coordinator.animate { [weak self] context in
|
||||
guard let self = self else { return }
|
||||
self.updateBehavior(size: size)
|
||||
} completion: { context in
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private func updateBehavior(size: CGSize) {
|
||||
// fix secondary too small on iPad mini issue
|
||||
switch contentSplitViewController.currentSupplementaryTab {
|
||||
case .search:
|
||||
hide(.primary)
|
||||
default:
|
||||
if size.width > 960 {
|
||||
preferredDisplayMode = .oneBesideSecondary
|
||||
preferredSplitBehavior = .tile
|
||||
show(.primary)
|
||||
} else {
|
||||
preferredDisplayMode = .oneBesideSecondary
|
||||
preferredSplitBehavior = .displace
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - SidebarViewControllerDelegate
|
||||
extension RootSplitViewController: SidebarViewControllerDelegate {
|
||||
|
||||
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) {
|
||||
|
||||
guard let index = MainTabBarController.Tab.allCases.firstIndex(of: tab) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
currentSupplementaryTab = tab
|
||||
setViewController(supplementaryViewControllers[index], for: .supplementary)
|
||||
}
|
||||
|
||||
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectSearchHistory searchHistoryViewModel: SidebarViewModel.SearchHistoryViewModel) {
|
||||
// self.sidebarViewController(sidebarViewController, didSelectTab: .search)
|
||||
|
||||
let supplementaryViewController = viewController(for: .supplementary)
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
managedObjectContext.perform {
|
||||
let searchHistory = managedObjectContext.object(with: searchHistoryViewModel.searchHistoryObjectID) as! SearchHistory
|
||||
if let account = searchHistory.account {
|
||||
DispatchQueue.main.async {
|
||||
let profileViewModel = CachedProfileViewModel(context: self.context, mastodonUser: account)
|
||||
self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: supplementaryViewController, transition: .show)
|
||||
}
|
||||
} else if let hashtag = searchHistory.hashtag {
|
||||
DispatchQueue.main.async {
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: self.context, hashtag: hashtag.name)
|
||||
self.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: supplementaryViewController, transition: .show)
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
hide(.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,76 +108,85 @@ extension RootSplitViewController: SidebarViewControllerDelegate {
|
|||
// MARK: - UISplitViewControllerDelegate
|
||||
extension RootSplitViewController: UISplitViewControllerDelegate {
|
||||
|
||||
// .regular to .compact
|
||||
// move navigation stack from .supplementary & .secondary to .compact
|
||||
func splitViewController(
|
||||
_ svc: UISplitViewController,
|
||||
topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column
|
||||
) -> UISplitViewController.Column {
|
||||
switch proposedTopColumn {
|
||||
case .compact:
|
||||
guard let index = MainTabBarController.Tab.allCases.firstIndex(of: currentSupplementaryTab) else {
|
||||
assertionFailure()
|
||||
break
|
||||
}
|
||||
mainTabBarController.selectedIndex = index
|
||||
mainTabBarController.currentTab.value = currentSupplementaryTab
|
||||
|
||||
guard let navigationController = mainTabBarController.selectedViewController as? UINavigationController else { break }
|
||||
navigationController.popToRootViewController(animated: false)
|
||||
var viewControllers = navigationController.viewControllers // init navigation stack with topMost
|
||||
|
||||
if let supplementaryNavigationController = viewController(for: .supplementary) as? UINavigationController {
|
||||
// append supplementary
|
||||
viewControllers.append(contentsOf: supplementaryNavigationController.popToRootViewController(animated: true) ?? [])
|
||||
}
|
||||
if let secondaryNavigationController = viewController(for: .secondary) as? UINavigationController {
|
||||
// append secondary
|
||||
viewControllers.append(contentsOf: secondaryNavigationController.popToRootViewController(animated: true) ?? [])
|
||||
}
|
||||
// set navigation stack
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
return proposedTopColumn
|
||||
}
|
||||
|
||||
// .compact to .regular
|
||||
// restore navigation stack to .supplementary & .secondary
|
||||
func splitViewController(
|
||||
_ svc: UISplitViewController,
|
||||
displayModeForExpandingToProposedDisplayMode proposedDisplayMode: UISplitViewController.DisplayMode
|
||||
) -> UISplitViewController.DisplayMode {
|
||||
let compactNavigationController = mainTabBarController.selectedViewController as? UINavigationController
|
||||
|
||||
if let topMost = compactNavigationController?.topMost,
|
||||
topMost is AccountListViewController {
|
||||
topMost.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
|
||||
let viewControllers = compactNavigationController?.popToRootViewController(animated: true) ?? []
|
||||
|
||||
var supplementaryViewControllers: [UIViewController] = []
|
||||
var secondaryViewControllers: [UIViewController] = []
|
||||
for viewController in viewControllers {
|
||||
if coordinator.secondaryStackHashValues.contains(viewController.hashValue) {
|
||||
secondaryViewControllers.append(viewController)
|
||||
} else {
|
||||
supplementaryViewControllers.append(viewController)
|
||||
}
|
||||
// // .regular to .compact
|
||||
// // move navigation stack from .supplementary & .secondary to .compact
|
||||
// func splitViewController(
|
||||
// _ svc: UISplitViewController,
|
||||
// topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column
|
||||
// ) -> UISplitViewController.Column {
|
||||
// switch proposedTopColumn {
|
||||
// case .compact:
|
||||
// guard let index = MainTabBarController.Tab.allCases.firstIndex(of: currentSupplementaryTab) else {
|
||||
// assertionFailure()
|
||||
// break
|
||||
// }
|
||||
// mainTabBarController.selectedIndex = index
|
||||
// mainTabBarController.currentTab.value = currentSupplementaryTab
|
||||
//
|
||||
// guard let navigationController = mainTabBarController.selectedViewController as? UINavigationController else { break }
|
||||
// navigationController.popToRootViewController(animated: false)
|
||||
// var viewControllers = navigationController.viewControllers // init navigation stack with topMost
|
||||
//
|
||||
// if let supplementaryNavigationController = viewController(for: .supplementary) as? UINavigationController {
|
||||
// // append supplementary
|
||||
// viewControllers.append(contentsOf: supplementaryNavigationController.popToRootViewController(animated: true) ?? [])
|
||||
// }
|
||||
// if let secondaryNavigationController = viewController(for: .secondary) as? UINavigationController {
|
||||
// // append secondary
|
||||
// viewControllers.append(contentsOf: secondaryNavigationController.popToRootViewController(animated: true) ?? [])
|
||||
// }
|
||||
// // set navigation stack
|
||||
// navigationController.setViewControllers(viewControllers, animated: false)
|
||||
//
|
||||
// default:
|
||||
// assertionFailure()
|
||||
// }
|
||||
//
|
||||
// return proposedTopColumn
|
||||
// }
|
||||
//
|
||||
// // .compact to .regular
|
||||
// // restore navigation stack to .supplementary & .secondary
|
||||
// func splitViewController(
|
||||
// _ svc: UISplitViewController,
|
||||
// displayModeForExpandingToProposedDisplayMode proposedDisplayMode: UISplitViewController.DisplayMode
|
||||
// ) -> UISplitViewController.DisplayMode {
|
||||
// let compactNavigationController = mainTabBarController.selectedViewController as? UINavigationController
|
||||
//
|
||||
// if let topMost = compactNavigationController?.topMost,
|
||||
// topMost is AccountListViewController {
|
||||
// topMost.dismiss(animated: false, completion: nil)
|
||||
// }
|
||||
//
|
||||
// let viewControllers = compactNavigationController?.popToRootViewController(animated: true) ?? []
|
||||
//
|
||||
// var supplementaryViewControllers: [UIViewController] = []
|
||||
// var secondaryViewControllers: [UIViewController] = []
|
||||
// for viewController in viewControllers {
|
||||
// if coordinator.secondaryStackHashValues.contains(viewController.hashValue) {
|
||||
// secondaryViewControllers.append(viewController)
|
||||
// } else {
|
||||
// supplementaryViewControllers.append(viewController)
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// if let supplementary = viewController(for: .supplementary) as? UINavigationController {
|
||||
// supplementary.setViewControllers(supplementary.viewControllers + supplementaryViewControllers, animated: false)
|
||||
// }
|
||||
// if let secondaryNavigationController = viewController(for: .secondary) as? UINavigationController {
|
||||
// secondaryNavigationController.setViewControllers(secondaryNavigationController.viewControllers + secondaryViewControllers, animated: false)
|
||||
// }
|
||||
//
|
||||
// return proposedDisplayMode
|
||||
// }
|
||||
|
||||
}
|
||||
if let supplementary = viewController(for: .supplementary) as? UINavigationController {
|
||||
supplementary.setViewControllers(supplementary.viewControllers + supplementaryViewControllers, animated: false)
|
||||
}
|
||||
if let secondaryNavigationController = viewController(for: .secondary) as? UINavigationController {
|
||||
secondaryNavigationController.setViewControllers(secondaryNavigationController.viewControllers + secondaryViewControllers, animated: false)
|
||||
}
|
||||
|
||||
return proposedDisplayMode
|
||||
}
|
||||
|
||||
}
|
||||
//extension UIView {
|
||||
// func setNeedsLayoutForSubviews() {
|
||||
// self.subviews.forEach({
|
||||
// $0.setNeedsLayout()
|
||||
// $0.setNeedsLayoutForSubviews()
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -12,7 +12,6 @@ import CoreDataStack
|
|||
|
||||
protocol SidebarViewControllerDelegate: AnyObject {
|
||||
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab)
|
||||
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectSearchHistory searchHistoryViewModel: SidebarViewModel.SearchHistoryViewModel)
|
||||
}
|
||||
|
||||
final class SidebarViewController: UIViewController, NeedsDependency {
|
||||
|
@ -21,28 +20,46 @@ final class SidebarViewController: UIViewController, NeedsDependency {
|
|||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
var observations = Set<NSKeyValueObservation>()
|
||||
var viewModel: SidebarViewModel!
|
||||
|
||||
weak var delegate: SidebarViewControllerDelegate?
|
||||
|
||||
let settingBarButtonItem: UIBarButtonItem = {
|
||||
let barButtonItem = UIBarButtonItem()
|
||||
barButtonItem.tintColor = Asset.Colors.brandBlue.color
|
||||
barButtonItem.image = UIImage(systemName: "gear")?.withRenderingMode(.alwaysTemplate)
|
||||
return barButtonItem
|
||||
}()
|
||||
|
||||
static func createLayout() -> UICollectionViewLayout {
|
||||
let layout = UICollectionViewCompositionalLayout() { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
|
||||
var configuration = UICollectionLayoutListConfiguration(appearance: .sidebar)
|
||||
configuration.backgroundColor = .clear
|
||||
if sectionIndex == SidebarViewModel.Section.tab.rawValue {
|
||||
// with indentation
|
||||
configuration.headerMode = .none
|
||||
} else {
|
||||
// remove indentation
|
||||
configuration.headerMode = .firstItemInSection
|
||||
configuration.showsSeparators = false
|
||||
let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
|
||||
switch sectionIndex {
|
||||
case 0:
|
||||
let header = NSCollectionLayoutBoundarySupplementaryItem(
|
||||
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
|
||||
heightDimension: .estimated(100)),
|
||||
elementKind: UICollectionView.elementKindSectionHeader,
|
||||
alignment: .top
|
||||
)
|
||||
section.boundarySupplementaryItems = [header]
|
||||
default:
|
||||
break
|
||||
}
|
||||
return section
|
||||
}
|
||||
return layout
|
||||
}
|
||||
|
||||
let collectionView: UICollectionView = {
|
||||
let layout = SidebarViewController.createLayout()
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
collectionView.alwaysBounceVertical = false
|
||||
collectionView.backgroundColor = .clear
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
static func createSecondaryLayout() -> UICollectionViewLayout {
|
||||
let layout = UICollectionViewCompositionalLayout() { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
|
||||
var configuration = UICollectionLayoutListConfiguration(appearance: .sidebar)
|
||||
configuration.backgroundColor = .clear
|
||||
configuration.showsSeparators = false
|
||||
let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
|
||||
return section
|
||||
|
@ -50,12 +67,15 @@ final class SidebarViewController: UIViewController, NeedsDependency {
|
|||
return layout
|
||||
}
|
||||
|
||||
let collectionView: UICollectionView = {
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: SidebarViewController.createLayout())
|
||||
let secondaryCollectionView: UICollectionView = {
|
||||
let layout = SidebarViewController.createSecondaryLayout()
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
collectionView.isScrollEnabled = false
|
||||
collectionView.alwaysBounceVertical = false
|
||||
collectionView.backgroundColor = .clear
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
var secondaryCollectionViewHeightLayoutConstraint: NSLayoutConstraint!
|
||||
}
|
||||
|
||||
extension SidebarViewController {
|
||||
|
@ -63,23 +83,7 @@ extension SidebarViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
viewModel.context.authenticationService.activeMastodonAuthenticationBox
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] activeMastodonAuthenticationBox in
|
||||
guard let self = self else { return }
|
||||
let domain = activeMastodonAuthenticationBox?.domain
|
||||
self.navigationItem.backBarButtonItem = {
|
||||
let barButtonItem = UIBarButtonItem()
|
||||
barButtonItem.image = UIImage(systemName: "sidebar.leading")
|
||||
return barButtonItem
|
||||
}()
|
||||
self.navigationItem.title = domain
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
navigationItem.rightBarButtonItem = settingBarButtonItem
|
||||
settingBarButtonItem.target = self
|
||||
settingBarButtonItem.action = #selector(SidebarViewController.settingBarButtonItemPressed(_:))
|
||||
navigationController?.navigationBar.prefersLargeTitles = true
|
||||
navigationController?.setNavigationBarHidden(true, animated: false)
|
||||
|
||||
setupBackground(theme: ThemeService.shared.currentTheme.value)
|
||||
ThemeService.shared.currentTheme
|
||||
|
@ -99,65 +103,101 @@ extension SidebarViewController {
|
|||
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
||||
secondaryCollectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(secondaryCollectionView)
|
||||
secondaryCollectionViewHeightLayoutConstraint = secondaryCollectionView.heightAnchor.constraint(equalToConstant: 44).priority(.required - 1)
|
||||
NSLayoutConstraint.activate([
|
||||
secondaryCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
secondaryCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: secondaryCollectionView.bottomAnchor),
|
||||
secondaryCollectionViewHeightLayoutConstraint,
|
||||
])
|
||||
|
||||
collectionView.delegate = self
|
||||
viewModel.setupDiffableDataSource(collectionView: collectionView)
|
||||
secondaryCollectionView.delegate = self
|
||||
viewModel.setupDiffableDataSource(
|
||||
collectionView: collectionView,
|
||||
secondaryCollectionView: secondaryCollectionView
|
||||
)
|
||||
|
||||
secondaryCollectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] secondaryCollectionView, _ in
|
||||
guard let self = self else { return }
|
||||
let height = secondaryCollectionView.contentSize.height
|
||||
self.secondaryCollectionViewHeightLayoutConstraint.constant = height
|
||||
self.collectionView.contentInset.bottom = height
|
||||
}
|
||||
.store(in: &observations)
|
||||
}
|
||||
|
||||
private func setupBackground(theme: Theme) {
|
||||
let color: UIColor = theme.sidebarBackgroundColor
|
||||
let barAppearance = UINavigationBarAppearance()
|
||||
barAppearance.configureWithOpaqueBackground()
|
||||
barAppearance.backgroundColor = color
|
||||
barAppearance.shadowColor = .clear
|
||||
barAppearance.shadowImage = UIImage() // remove separator line
|
||||
navigationItem.standardAppearance = barAppearance
|
||||
navigationItem.compactAppearance = barAppearance
|
||||
navigationItem.scrollEdgeAppearance = barAppearance
|
||||
if #available(iOS 15.0, *) {
|
||||
navigationItem.compactScrollEdgeAppearance = barAppearance
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
|
||||
view.backgroundColor = color
|
||||
collectionView.backgroundColor = color
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
coordinator.animate { context in
|
||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||
// // do nothing
|
||||
} completion: { [weak self] context in
|
||||
// guard let self = self else { return }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SidebarViewController {
|
||||
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
guard let setting = context.settingService.currentSetting.value else { return }
|
||||
let settingsViewModel = SettingsViewModel(context: context, setting: setting)
|
||||
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDelegate
|
||||
extension SidebarViewController: UICollectionViewDelegate {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
switch collectionView {
|
||||
case self.collectionView:
|
||||
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 .searchHistory(let viewModel):
|
||||
delegate?.sidebarViewController(self, didSelectSearchHistory: viewModel)
|
||||
case .header:
|
||||
break
|
||||
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))
|
||||
case .setting:
|
||||
guard let setting = context.settingService.currentSetting.value else { return }
|
||||
let settingsViewModel = SettingsViewModel(context: context, setting: setting)
|
||||
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
case .compose:
|
||||
assertionFailure()
|
||||
}
|
||||
case secondaryCollectionView:
|
||||
guard let diffableDataSource = viewModel.secondaryDiffableDataSource else { return }
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
switch item {
|
||||
case .compose:
|
||||
let composeViewModel = ComposeViewModel(context: context, composeKind: .post)
|
||||
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
// switch item {
|
||||
// case .tab(let tab):
|
||||
// delegate?.sidebarViewController(self, didSelectTab: tab)
|
||||
// case .searchHistory(let viewModel):
|
||||
// delegate?.sidebarViewController(self, didSelectSearchHistory: viewModel)
|
||||
// case .header:
|
||||
// break
|
||||
// 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))
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ final class SidebarViewModel {
|
|||
|
||||
// output
|
||||
var diffableDataSource: UICollectionViewDiffableDataSource<Section, Item>?
|
||||
var secondaryDiffableDataSource: UICollectionViewDiffableDataSource<Section, Item>?
|
||||
|
||||
let activeMastodonAuthenticationObjectID = CurrentValueSubject<NSManagedObjectID?, Never>(nil)
|
||||
|
||||
init(context: AppContext) {
|
||||
|
@ -47,38 +49,22 @@ final class SidebarViewModel {
|
|||
|
||||
extension SidebarViewModel {
|
||||
enum Section: Int, Hashable, CaseIterable {
|
||||
case tab
|
||||
case account
|
||||
case main
|
||||
case secondary
|
||||
}
|
||||
|
||||
enum Item: Hashable {
|
||||
case tab(MainTabBarController.Tab)
|
||||
case searchHistory(SearchHistoryViewModel)
|
||||
case header(HeaderViewModel)
|
||||
case account(AccountViewModel)
|
||||
case addAccount
|
||||
case setting
|
||||
case compose
|
||||
}
|
||||
|
||||
struct SearchHistoryViewModel: Hashable {
|
||||
let searchHistoryObjectID: NSManagedObjectID
|
||||
}
|
||||
|
||||
struct HeaderViewModel: Hashable {
|
||||
let title: String
|
||||
}
|
||||
|
||||
struct AccountViewModel: Hashable {
|
||||
let authenticationObjectID: NSManagedObjectID
|
||||
}
|
||||
|
||||
struct AddAccountViewModel: Hashable {
|
||||
let id = UUID()
|
||||
}
|
||||
}
|
||||
|
||||
extension SidebarViewModel {
|
||||
func setupDiffableDataSource(
|
||||
collectionView: UICollectionView
|
||||
collectionView: UICollectionView,
|
||||
secondaryCollectionView: UICollectionView
|
||||
) {
|
||||
let tabCellRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, MainTabBarController.Tab> { [weak self] cell, indexPath, item in
|
||||
guard let self = self else { return }
|
||||
|
@ -92,23 +78,10 @@ extension SidebarViewModel {
|
|||
return nil
|
||||
}
|
||||
}()
|
||||
let headline: MetaContent = {
|
||||
switch item {
|
||||
case .me:
|
||||
return PlaintextMetaContent(string: item.title)
|
||||
// TODO:
|
||||
// return PlaintextMetaContent(string: "Myself")
|
||||
default:
|
||||
return PlaintextMetaContent(string: item.title)
|
||||
}
|
||||
}()
|
||||
let needsOutlineDisclosure = item == .search
|
||||
cell.item = SidebarListContentView.Item(
|
||||
title: item.title,
|
||||
image: item.sidebarImage,
|
||||
imageURL: imageURL,
|
||||
headline: headline,
|
||||
subheadline: nil,
|
||||
needsOutlineDisclosure: needsOutlineDisclosure
|
||||
imageURL: imageURL
|
||||
)
|
||||
cell.setNeedsUpdateConfiguration()
|
||||
|
||||
|
@ -135,209 +108,91 @@ extension SidebarViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
let searchHistoryCellRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, SearchHistoryViewModel> { [weak self] cell, indexPath, item in
|
||||
let cellRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, SidebarListContentView.Item> { [weak self] cell, indexPath, item in
|
||||
guard let self = self else { return }
|
||||
let managedObjectContext = self.searchHistoryFetchedResultController.fetchedResultsController.managedObjectContext
|
||||
|
||||
guard let searchHistory = try? managedObjectContext.existingObject(with: item.searchHistoryObjectID) as? SearchHistory else { return }
|
||||
|
||||
if let account = searchHistory.account {
|
||||
let headline: MetaContent = {
|
||||
do {
|
||||
let content = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojiMeta)
|
||||
return try MastodonMetaContent.convert(document: content)
|
||||
} catch {
|
||||
return PlaintextMetaContent(string: account.displayNameWithFallback)
|
||||
}
|
||||
}()
|
||||
cell.item = SidebarListContentView.Item(
|
||||
image: .placeholder(color: .systemFill),
|
||||
imageURL: account.avatarImageURL(),
|
||||
headline: headline,
|
||||
subheadline: PlaintextMetaContent(string: "@" + account.acctWithDomain),
|
||||
needsOutlineDisclosure: false
|
||||
)
|
||||
} else if let hashtag = searchHistory.hashtag {
|
||||
let image = UIImage(systemName: "number.square.fill")!.withRenderingMode(.alwaysTemplate)
|
||||
let headline = PlaintextMetaContent(string: "#" + hashtag.name)
|
||||
cell.item = SidebarListContentView.Item(
|
||||
image: image,
|
||||
imageURL: nil,
|
||||
headline: headline,
|
||||
subheadline: nil,
|
||||
needsOutlineDisclosure: false
|
||||
)
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
cell.item = item
|
||||
cell.setNeedsUpdateConfiguration()
|
||||
}
|
||||
|
||||
let headerRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, HeaderViewModel> { (cell, indexPath, item) in
|
||||
var content = UIListContentConfiguration.sidebarHeader()
|
||||
content.text = item.title
|
||||
cell.contentConfiguration = content
|
||||
cell.accessories = [.outlineDisclosure()]
|
||||
}
|
||||
|
||||
let accountRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, AccountViewModel> { [weak self] (cell, indexPath, item) in
|
||||
guard let self = self else { return }
|
||||
|
||||
// accounts maybe already sign-out
|
||||
// check isDeleted before using
|
||||
guard let authentication = try? AppContext.shared.managedObjectContext.existingObject(with: item.authenticationObjectID) as? MastodonAuthentication,
|
||||
!authentication.isDeleted else {
|
||||
return
|
||||
}
|
||||
let user = authentication.user
|
||||
let imageURL = user.avatarImageURL()
|
||||
let headline: MetaContent = {
|
||||
do {
|
||||
let content = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojiMeta)
|
||||
return try MastodonMetaContent.convert(document: content)
|
||||
} catch {
|
||||
return PlaintextMetaContent(string: user.displayNameWithFallback)
|
||||
}
|
||||
}()
|
||||
cell.item = SidebarListContentView.Item(
|
||||
image: .placeholder(color: .systemFill),
|
||||
imageURL: imageURL,
|
||||
headline: headline,
|
||||
subheadline: PlaintextMetaContent(string: "@" + user.acctWithDomain),
|
||||
needsOutlineDisclosure: false
|
||||
)
|
||||
cell.setNeedsUpdateConfiguration()
|
||||
|
||||
// FIXME: use notification, not timer
|
||||
let accessToken = authentication.userAccessToken
|
||||
AppContext.shared.timestampUpdatePublisher
|
||||
.map { _ in UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken) }
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] count in
|
||||
guard let cell = cell else { return }
|
||||
cell._contentView?.badgeButton.setBadge(number: count)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
let authenticationObjectID = item.authenticationObjectID
|
||||
self.activeMastodonAuthenticationObjectID
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] objectID in
|
||||
guard let cell = cell else { return }
|
||||
cell._contentView?.checkmarkImageView.isHidden = authenticationObjectID != objectID
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
|
||||
let addAccountRegistration = UICollectionView.CellRegistration<SidebarAddAccountCollectionViewCell, AddAccountViewModel> { (cell, indexPath, item) in
|
||||
var content = UIListContentConfiguration.sidebarCell()
|
||||
content.text = L10n.Scene.AccountList.addAccount
|
||||
content.image = UIImage(systemName: "plus.square.fill")!
|
||||
|
||||
cell.contentConfiguration = content
|
||||
cell.accessories = []
|
||||
// header
|
||||
let headerRegistration = UICollectionView.SupplementaryRegistration<SidebarListHeaderView>(elementKind: UICollectionView.elementKindSectionHeader) { supplementaryView, elementKind, indexPath in
|
||||
// do nothing
|
||||
}
|
||||
|
||||
let _diffableDataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in
|
||||
switch item {
|
||||
case .tab(let tab):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: tabCellRegistration, for: indexPath, item: tab)
|
||||
case .searchHistory(let viewModel):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: searchHistoryCellRegistration, for: indexPath, item: viewModel)
|
||||
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())
|
||||
case .setting:
|
||||
let item = SidebarListContentView.Item(
|
||||
title: L10n.Common.Controls.Actions.settings,
|
||||
image: UIImage(systemName: "gear")!,
|
||||
imageURL: nil
|
||||
)
|
||||
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
|
||||
case .compose:
|
||||
let item = SidebarListContentView.Item(
|
||||
title: "Compose", // FIXME:
|
||||
image: UIImage(systemName: "square.and.pencil")!,
|
||||
imageURL: nil
|
||||
)
|
||||
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
|
||||
}
|
||||
}
|
||||
_diffableDataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
|
||||
switch elementKind {
|
||||
case UICollectionView.elementKindSectionHeader:
|
||||
return collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: indexPath)
|
||||
default:
|
||||
assertionFailure()
|
||||
return UICollectionReusableView()
|
||||
}
|
||||
}
|
||||
diffableDataSource = _diffableDataSource
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections(Section.allCases)
|
||||
_diffableDataSource.apply(snapshot)
|
||||
snapshot.appendSections([.main])
|
||||
|
||||
for section in Section.allCases {
|
||||
switch section {
|
||||
case .tab:
|
||||
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
||||
let items: [Item] = [
|
||||
.tab(.home),
|
||||
.tab(.search),
|
||||
.tab(.notification),
|
||||
.tab(.me),
|
||||
.setting,
|
||||
]
|
||||
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)
|
||||
}
|
||||
_diffableDataSource.apply(sectionSnapshot, to: .main)
|
||||
|
||||
|
||||
// secondary
|
||||
let _secondaryDiffableDataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: secondaryCollectionView) { collectionView, indexPath, item in
|
||||
guard case .compose = item else {
|
||||
assertionFailure()
|
||||
return UICollectionViewCell()
|
||||
}
|
||||
|
||||
// update .search tab
|
||||
searchHistoryFetchedResultController.objectIDs
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] objectIDs in
|
||||
guard let self = self else { return }
|
||||
guard let diffableDataSource = self.diffableDataSource else { return }
|
||||
|
||||
// update .search tab
|
||||
var sectionSnapshot = diffableDataSource.snapshot(for: .tab)
|
||||
|
||||
// remove children
|
||||
let searchHistorySnapshot = sectionSnapshot.snapshot(of: .tab(.search))
|
||||
sectionSnapshot.delete(searchHistorySnapshot.items)
|
||||
|
||||
// append children
|
||||
let managedObjectContext = self.searchHistoryFetchedResultController.fetchedResultsController.managedObjectContext
|
||||
let items: [Item] = objectIDs.compactMap { objectID -> Item? in
|
||||
guard let searchHistory = try? managedObjectContext.existingObject(with: objectID) as? SearchHistory else { return nil }
|
||||
guard searchHistory.account != nil || searchHistory.hashtag != nil else { return nil }
|
||||
let viewModel = SearchHistoryViewModel(searchHistoryObjectID: objectID)
|
||||
return Item.searchHistory(viewModel)
|
||||
let item = SidebarListContentView.Item(
|
||||
title: "Compose", // FIXME:
|
||||
image: UIImage(systemName: "square.and.pencil")!,
|
||||
imageURL: nil
|
||||
)
|
||||
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
|
||||
}
|
||||
sectionSnapshot.append(Array(items.prefix(5)), to: .tab(.search))
|
||||
sectionSnapshot.expand([.tab(.search)])
|
||||
// _secondaryDiffableDataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
|
||||
// return nil
|
||||
// }
|
||||
secondaryDiffableDataSource = _secondaryDiffableDataSource
|
||||
|
||||
// apply snapshot
|
||||
diffableDataSource.apply(sectionSnapshot, to: .tab, animatingDifferences: false)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
var secondarySnapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
secondarySnapshot.appendSections([.secondary])
|
||||
|
||||
// update .me tab and .account section
|
||||
context.authenticationService.mastodonAuthentications
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] authentications in
|
||||
guard let self = self else { return }
|
||||
guard let diffableDataSource = self.diffableDataSource else { return }
|
||||
// tab
|
||||
var snapshot = diffableDataSource.snapshot()
|
||||
snapshot.reloadItems([.tab(.me)])
|
||||
diffableDataSource.apply(snapshot)
|
||||
|
||||
// account
|
||||
var accountSectionSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
||||
let headerItem = Item.header(HeaderViewModel(title: "Accounts"))
|
||||
accountSectionSnapshot.append([headerItem], to: nil)
|
||||
let accountItems = authentications.map { authentication in
|
||||
Item.account(AccountViewModel(authenticationObjectID: authentication.objectID))
|
||||
}
|
||||
accountSectionSnapshot.append(accountItems, to: headerItem)
|
||||
accountSectionSnapshot.append([.addAccount], to: headerItem)
|
||||
accountSectionSnapshot.expand([headerItem])
|
||||
diffableDataSource.apply(accountSectionSnapshot, to: .account)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
var secondarySectionSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
||||
let secondarySectionItems: [Item] = [
|
||||
.compose,
|
||||
]
|
||||
secondarySectionSnapshot.append(secondarySectionItems, to: nil)
|
||||
_secondaryDiffableDataSource.apply(secondarySectionSnapshot, to: .secondary)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,21 +60,6 @@ extension SidebarListCollectionViewCell {
|
|||
newBackgroundConfiguration.backgroundColorTransformer = .init { $0.withAlphaComponent(0.8) }
|
||||
}
|
||||
|
||||
|
||||
backgroundConfiguration = newBackgroundConfiguration
|
||||
|
||||
let needsOutlineDisclosure = item?.needsOutlineDisclosure ?? false
|
||||
if !needsOutlineDisclosure {
|
||||
accessories = []
|
||||
} else {
|
||||
let tintColor: UIColor = state.isHighlighted || state.isSelected ? .white : Asset.Colors.brandBlue.color
|
||||
accessories = [
|
||||
UICellAccessory.outlineDisclosure(
|
||||
displayed: .always,
|
||||
options: UICellAccessory.OutlineDisclosureOptions(tintColor: tintColor),
|
||||
actionHandler: nil
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,15 +15,10 @@ final class SidebarListContentView: UIView, UIContentView {
|
|||
let logger = Logger(subsystem: "SidebarListContentView", category: "UI")
|
||||
|
||||
let imageView = UIImageView()
|
||||
let animationImageView = FLAnimatedImageView() // for animation image
|
||||
let headlineLabel = MetaLabel(style: .sidebarHeadline(isSelected: false))
|
||||
let subheadlineLabel = MetaLabel(style: .sidebarSubheadline(isSelected: false))
|
||||
let badgeButton = BadgeButton()
|
||||
let checkmarkImageView: UIImageView = {
|
||||
let image = UIImage(systemName: "checkmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .semibold))
|
||||
let imageView = UIImageView(image: image)
|
||||
imageView.tintColor = .label
|
||||
return imageView
|
||||
let avatarButton: CircleAvatarButton = {
|
||||
let button = CircleAvatarButton()
|
||||
button.borderWidth = 2
|
||||
return button
|
||||
}()
|
||||
|
||||
private var currentConfiguration: ContentConfiguration!
|
||||
|
@ -53,93 +48,31 @@ final class SidebarListContentView: UIView, UIContentView {
|
|||
|
||||
extension SidebarListContentView {
|
||||
private func _init() {
|
||||
let imageViewContainer = UIView()
|
||||
imageViewContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(imageViewContainer)
|
||||
NSLayoutConstraint.activate([
|
||||
imageViewContainer.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
|
||||
imageViewContainer.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
])
|
||||
imageViewContainer.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
imageViewContainer.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||
|
||||
animationImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageViewContainer.addSubview(animationImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
animationImageView.centerXAnchor.constraint(equalTo: imageViewContainer.centerXAnchor),
|
||||
animationImageView.centerYAnchor.constraint(equalTo: imageViewContainer.centerYAnchor),
|
||||
animationImageView.widthAnchor.constraint(equalTo: imageViewContainer.widthAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
animationImageView.heightAnchor.constraint(equalTo: imageViewContainer.heightAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
])
|
||||
animationImageView.setContentHuggingPriority(.defaultLow - 10, for: .vertical)
|
||||
animationImageView.setContentHuggingPriority(.defaultLow - 10, for: .horizontal)
|
||||
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageViewContainer.addSubview(imageView)
|
||||
addSubview(imageView)
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.centerXAnchor.constraint(equalTo: imageViewContainer.centerXAnchor),
|
||||
imageView.centerYAnchor.constraint(equalTo: imageViewContainer.centerYAnchor),
|
||||
imageView.widthAnchor.constraint(equalTo: imageViewContainer.widthAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
imageView.heightAnchor.constraint(equalTo: imageViewContainer.heightAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
])
|
||||
imageView.setContentHuggingPriority(.defaultLow - 10, for: .vertical)
|
||||
imageView.setContentHuggingPriority(.defaultLow - 10, for: .horizontal)
|
||||
|
||||
let textContainer = UIStackView()
|
||||
textContainer.axis = .vertical
|
||||
textContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(textContainer)
|
||||
NSLayoutConstraint.activate([
|
||||
textContainer.topAnchor.constraint(equalTo: topAnchor, constant: 10),
|
||||
textContainer.leadingAnchor.constraint(equalTo: imageViewContainer.trailingAnchor, constant: 10),
|
||||
// textContainer.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||
bottomAnchor.constraint(equalTo: textContainer.bottomAnchor, constant: 12),
|
||||
imageView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
|
||||
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
bottomAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 16),
|
||||
imageView.widthAnchor.constraint(equalToConstant: 40).priority(.required - 1),
|
||||
imageView.heightAnchor.constraint(equalToConstant: 40).priority(.required - 1),
|
||||
])
|
||||
|
||||
textContainer.addArrangedSubview(headlineLabel)
|
||||
textContainer.addArrangedSubview(subheadlineLabel)
|
||||
headlineLabel.setContentHuggingPriority(.required - 9, for: .vertical)
|
||||
headlineLabel.setContentCompressionResistancePriority(.required - 9, for: .vertical)
|
||||
subheadlineLabel.setContentHuggingPriority(.required - 10, for: .vertical)
|
||||
subheadlineLabel.setContentCompressionResistancePriority(.required - 10, for: .vertical)
|
||||
|
||||
badgeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(badgeButton)
|
||||
avatarButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(avatarButton)
|
||||
NSLayoutConstraint.activate([
|
||||
badgeButton.leadingAnchor.constraint(equalTo: textContainer.trailingAnchor, constant: 4),
|
||||
badgeButton.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
badgeButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 16).priority(.required - 1),
|
||||
badgeButton.widthAnchor.constraint(equalTo: badgeButton.heightAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
avatarButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
|
||||
avatarButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
|
||||
avatarButton.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.0).priority(.required - 2),
|
||||
avatarButton.heightAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: 1.0).priority(.required - 2),
|
||||
])
|
||||
badgeButton.setContentHuggingPriority(.required - 10, for: .horizontal)
|
||||
badgeButton.setContentCompressionResistancePriority(.required - 10, for: .horizontal)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
imageViewContainer.heightAnchor.constraint(equalTo: headlineLabel.heightAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
imageViewContainer.widthAnchor.constraint(equalTo: imageViewContainer.heightAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
])
|
||||
|
||||
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(checkmarkImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
checkmarkImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
checkmarkImageView.leadingAnchor.constraint(equalTo: badgeButton.trailingAnchor, constant: 16),
|
||||
checkmarkImageView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||
])
|
||||
checkmarkImageView.setContentHuggingPriority(.required - 9, for: .horizontal)
|
||||
checkmarkImageView.setContentCompressionResistancePriority(.required - 9, for: .horizontal)
|
||||
|
||||
animationImageView.isUserInteractionEnabled = false
|
||||
headlineLabel.isUserInteractionEnabled = false
|
||||
subheadlineLabel.isUserInteractionEnabled = false
|
||||
avatarButton.setContentHuggingPriority(.defaultLow - 10, for: .vertical)
|
||||
avatarButton.setContentHuggingPriority(.defaultLow - 10, for: .horizontal)
|
||||
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
animationImageView.contentMode = .scaleAspectFit
|
||||
avatarButton.contentMode = .scaleAspectFit
|
||||
imageView.tintColor = Asset.Colors.brandBlue.color
|
||||
animationImageView.tintColor = Asset.Colors.brandBlue.color
|
||||
|
||||
badgeButton.setBadge(number: 0)
|
||||
checkmarkImageView.isHidden = true
|
||||
avatarButton.tintColor = Asset.Colors.brandBlue.color
|
||||
}
|
||||
|
||||
private func apply(configuration: ContentConfiguration) {
|
||||
|
@ -152,31 +85,20 @@ extension SidebarListContentView {
|
|||
|
||||
// configure state
|
||||
imageView.tintColor = item.isSelected ? .white : Asset.Colors.brandBlue.color
|
||||
animationImageView.tintColor = item.isSelected ? .white : Asset.Colors.brandBlue.color
|
||||
headlineLabel.setup(style: .sidebarHeadline(isSelected: item.isSelected))
|
||||
subheadlineLabel.setup(style: .sidebarSubheadline(isSelected: item.isSelected))
|
||||
avatarButton.tintColor = item.isSelected ? .white : Asset.Colors.brandBlue.color
|
||||
|
||||
// configure model
|
||||
imageView.isHidden = item.imageURL != nil
|
||||
animationImageView.isHidden = item.imageURL == nil
|
||||
avatarButton.isHidden = item.imageURL == nil
|
||||
imageView.image = item.image.withRenderingMode(.alwaysTemplate)
|
||||
animationImageView.setImage(
|
||||
avatarButton.avatarImageView.setImage(
|
||||
url: item.imageURL,
|
||||
placeholder: animationImageView.image ?? .placeholder(color: .systemFill), // reuse to avoid blink
|
||||
placeholder: avatarButton.avatarImageView.image ?? .placeholder(color: .systemFill), // reuse to avoid blink
|
||||
scaleToSize: nil
|
||||
)
|
||||
animationImageView.layer.masksToBounds = true
|
||||
animationImageView.layer.cornerCurve = .continuous
|
||||
animationImageView.layer.cornerRadius = 4
|
||||
|
||||
headlineLabel.configure(content: item.headline)
|
||||
|
||||
if let subheadline = item.subheadline {
|
||||
subheadlineLabel.configure(content: subheadline)
|
||||
subheadlineLabel.isHidden = false
|
||||
} else {
|
||||
subheadlineLabel.isHidden = true
|
||||
}
|
||||
avatarButton.layer.masksToBounds = true
|
||||
avatarButton.layer.cornerCurve = .continuous
|
||||
avatarButton.layer.cornerRadius = 4
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,27 +108,22 @@ extension SidebarListContentView {
|
|||
var isSelected: Bool = false
|
||||
|
||||
// model
|
||||
let title: String
|
||||
let image: UIImage
|
||||
let imageURL: URL?
|
||||
let headline: MetaContent
|
||||
let subheadline: MetaContent?
|
||||
|
||||
let needsOutlineDisclosure: Bool
|
||||
|
||||
static func == (lhs: SidebarListContentView.Item, rhs: SidebarListContentView.Item) -> Bool {
|
||||
return lhs.isSelected == rhs.isSelected
|
||||
&& lhs.title == rhs.title
|
||||
&& lhs.image == rhs.image
|
||||
&& lhs.imageURL == rhs.imageURL
|
||||
&& lhs.headline.string == rhs.headline.string
|
||||
&& lhs.subheadline?.string == rhs.subheadline?.string
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(isSelected)
|
||||
hasher.combine(title)
|
||||
hasher.combine(image)
|
||||
imageURL.flatMap { hasher.combine($0) }
|
||||
hasher.combine(headline.string)
|
||||
subheadline.flatMap { hasher.combine($0.string) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// SidebarListHeaderView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-10-28.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
final class SidebarListHeaderView: UICollectionReusableView {
|
||||
|
||||
let imageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.image = Asset.Scene.Sidebar.logo.image
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SidebarListHeaderView {
|
||||
private func _init() {
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(imageView)
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
|
||||
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
bottomAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 16),
|
||||
imageView.widthAnchor.constraint(equalToConstant: 44).priority(.required - 1),
|
||||
imageView.heightAnchor.constraint(equalToConstant: 44).priority(.required - 1),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -9,12 +9,15 @@ import UIKit
|
|||
|
||||
final class CircleAvatarButton: AvatarButton {
|
||||
|
||||
var borderColor: CGColor = UIColor.systemFill.cgColor
|
||||
var borderWidth: CGFloat = 1.0
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
layer.masksToBounds = true
|
||||
layer.cornerRadius = frame.width * 0.5
|
||||
layer.borderColor = UIColor.systemFill.cgColor
|
||||
layer.borderWidth = 1
|
||||
layer.borderColor = borderColor
|
||||
layer.borderWidth = borderWidth
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ struct SystemTheme: Theme {
|
|||
|
||||
let navigationBarBackgroundColor = Asset.Theme.System.navigationBarBackground.color
|
||||
|
||||
let sidebarBackgroundColor = Asset.Theme.Mastodon.sidebarBackground.color
|
||||
let sidebarBackgroundColor = Asset.Theme.System.sidebarBackground.color
|
||||
|
||||
let tabBarBackgroundColor = Asset.Theme.System.tabBarBackground.color
|
||||
let tabBarItemSelectedIconColor = Asset.Colors.brandBlue.color
|
||||
|
|
Loading…
Reference in New Issue