feat: update sidebar UI
This commit is contained in:
parent
98bec294f6
commit
d8de3c4f65
|
@ -199,6 +199,8 @@
|
||||||
DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947126A7D2D70088FB11 /* AvatarButton.swift */; };
|
DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947126A7D2D70088FB11 /* AvatarButton.swift */; };
|
||||||
DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */; };
|
DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */; };
|
||||||
DB0E91EA26A9675100BD2ACC /* MetaLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0E91E926A9675100BD2ACC /* MetaLabel.swift */; };
|
DB0E91EA26A9675100BD2ACC /* MetaLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0E91E926A9675100BD2ACC /* MetaLabel.swift */; };
|
||||||
|
DB0EF72B26FDB1D200347686 /* SidebarListCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */; };
|
||||||
|
DB0EF72E26FDB24F00347686 /* SidebarListContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0EF72D26FDB24F00347686 /* SidebarListContentView.swift */; };
|
||||||
DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */; };
|
DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */; };
|
||||||
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
||||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; };
|
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; };
|
||||||
|
@ -954,6 +956,8 @@
|
||||||
DB0C947126A7D2D70088FB11 /* AvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarButton.swift; sourceTree = "<group>"; };
|
DB0C947126A7D2D70088FB11 /* AvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarButton.swift; sourceTree = "<group>"; };
|
||||||
DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = "<group>"; };
|
DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = "<group>"; };
|
||||||
DB0E91E926A9675100BD2ACC /* MetaLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaLabel.swift; sourceTree = "<group>"; };
|
DB0E91E926A9675100BD2ACC /* MetaLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaLabel.swift; sourceTree = "<group>"; };
|
||||||
|
DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
DB0EF72D26FDB24F00347686 /* SidebarListContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListContentView.swift; sourceTree = "<group>"; };
|
||||||
DB0F814D264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
DB0F814D264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2082,6 +2086,15 @@
|
||||||
path = Button;
|
path = Button;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
DB0EF72C26FDB1D600347686 /* View */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */,
|
||||||
|
DB0EF72D26FDB24F00347686 /* SidebarListContentView.swift */,
|
||||||
|
);
|
||||||
|
path = View;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
DB1D187125EF5BBD003F1F23 /* TableView */ = {
|
DB1D187125EF5BBD003F1F23 /* TableView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2526,6 +2539,7 @@
|
||||||
DB852D1A26FAED0100FC9D81 /* Sidebar */ = {
|
DB852D1A26FAED0100FC9D81 /* Sidebar */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
DB0EF72C26FDB1D600347686 /* View */,
|
||||||
DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */,
|
DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */,
|
||||||
DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */,
|
DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */,
|
||||||
);
|
);
|
||||||
|
@ -4136,6 +4150,7 @@
|
||||||
DB9D6C0E25E4F9780051B173 /* MosaicImageViewContainer.swift in Sources */,
|
DB9D6C0E25E4F9780051B173 /* MosaicImageViewContainer.swift in Sources */,
|
||||||
DBCBCC0D2680B908000F5B51 /* HomeTimelinePreference.swift in Sources */,
|
DBCBCC0D2680B908000F5B51 /* HomeTimelinePreference.swift in Sources */,
|
||||||
DB71FD3625F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift in Sources */,
|
DB71FD3625F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift in Sources */,
|
||||||
|
DB0EF72E26FDB24F00347686 /* SidebarListContentView.swift in Sources */,
|
||||||
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */,
|
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */,
|
||||||
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
|
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
|
||||||
2D7867192625B77500211898 /* NotificationItem.swift in Sources */,
|
2D7867192625B77500211898 /* NotificationItem.swift in Sources */,
|
||||||
|
@ -4151,6 +4166,7 @@
|
||||||
2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */,
|
2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */,
|
||||||
5B90C48526259BF10002E742 /* APIService+Subscriptions.swift in Sources */,
|
5B90C48526259BF10002E742 /* APIService+Subscriptions.swift in Sources */,
|
||||||
0F20223926146553000C64BF /* Array.swift in Sources */,
|
0F20223926146553000C64BF /* Array.swift in Sources */,
|
||||||
|
DB0EF72B26FDB1D200347686 /* SidebarListCollectionViewCell.swift in Sources */,
|
||||||
5B90C460262599800002E742 /* SettingsAppearanceTableViewCell.swift in Sources */,
|
5B90C460262599800002E742 /* SettingsAppearanceTableViewCell.swift in Sources */,
|
||||||
DB0C946B26A700AB0088FB11 /* MastodonUser+Property.swift in Sources */,
|
DB0C946B26A700AB0088FB11 /* MastodonUser+Property.swift in Sources */,
|
||||||
DB8AF54425C13647002E6C99 /* SceneCoordinator.swift in Sources */,
|
DB8AF54425C13647002E6C99 /* SceneCoordinator.swift in Sources */,
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
<key>AppShared.xcscheme_^#shared#^_</key>
|
<key>AppShared.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>59</integer>
|
<integer>36</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>62</integer>
|
<integer>35</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>60</integer>
|
<integer>37</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>61</integer>
|
<integer>38</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -216,6 +216,15 @@
|
||||||
"revision": "dad97167bf1be16aeecd109130900995dd01c515",
|
"revision": "dad97167bf1be16aeecd109130900995dd01c515",
|
||||||
"version": "2.6.0"
|
"version": "2.6.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "UITextView+Placeholder",
|
||||||
|
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "20f513ded04a040cdf5467f0891849b1763ede3b",
|
||||||
|
"version": "1.4.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -113,17 +113,18 @@ extension SceneCoordinator {
|
||||||
|
|
||||||
extension SceneCoordinator {
|
extension SceneCoordinator {
|
||||||
|
|
||||||
// func setup() {
|
|
||||||
// let viewController = MainTabBarController(context: appContext, coordinator: self)
|
|
||||||
// sceneDelegate.window?.rootViewController = viewController
|
|
||||||
// tabBarController = viewController
|
|
||||||
// }
|
|
||||||
|
|
||||||
func setup() {
|
func setup() {
|
||||||
|
switch UIDevice.current.userInterfaceIdiom {
|
||||||
|
case .phone:
|
||||||
|
let viewController = MainTabBarController(context: appContext, coordinator: self)
|
||||||
|
sceneDelegate.window?.rootViewController = viewController
|
||||||
|
tabBarController = viewController
|
||||||
|
default:
|
||||||
let splitViewController = RootSplitViewController(context: appContext, coordinator: self)
|
let splitViewController = RootSplitViewController(context: appContext, coordinator: self)
|
||||||
self.splitViewController = splitViewController
|
self.splitViewController = splitViewController
|
||||||
sceneDelegate.window?.rootViewController = splitViewController
|
sceneDelegate.window?.rootViewController = splitViewController
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setupOnboardingIfNeeds(animated: Bool) {
|
func setupOnboardingIfNeeds(animated: Bool) {
|
||||||
// Check user authentication status and show onboarding if needs
|
// Check user authentication status and show onboarding if needs
|
||||||
|
@ -177,7 +178,8 @@ extension SceneCoordinator {
|
||||||
case .show:
|
case .show:
|
||||||
if let splitViewController = splitViewController, !splitViewController.isCollapsed,
|
if let splitViewController = splitViewController, !splitViewController.isCollapsed,
|
||||||
let supplementaryViewController = splitViewController.viewController(for: .supplementary) as? UINavigationController,
|
let supplementaryViewController = splitViewController.viewController(for: .supplementary) as? UINavigationController,
|
||||||
(supplementaryViewController === presentingViewController || supplementaryViewController.viewControllers.contains(presentingViewController))
|
(supplementaryViewController === presentingViewController || supplementaryViewController.viewControllers.contains(presentingViewController)) ||
|
||||||
|
(presentingViewController is UserTimelineViewController && presentingViewController.view.isDescendant(of: supplementaryViewController.view))
|
||||||
{
|
{
|
||||||
fallthrough
|
fallthrough
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -22,6 +22,8 @@ extension MetaLabel {
|
||||||
case autoCompletion
|
case autoCompletion
|
||||||
case accountListName
|
case accountListName
|
||||||
case accountListUsername
|
case accountListUsername
|
||||||
|
case sidebarHeadline(isSelected: Bool)
|
||||||
|
case sidebarSubheadline(isSelected: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init(style: Style) {
|
convenience init(style: Style) {
|
||||||
|
@ -32,6 +34,10 @@ extension MetaLabel {
|
||||||
textContainer.lineBreakMode = .byTruncatingTail
|
textContainer.lineBreakMode = .byTruncatingTail
|
||||||
textContainer.lineFragmentPadding = 0
|
textContainer.lineFragmentPadding = 0
|
||||||
|
|
||||||
|
setup(style: style)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(style: Style) {
|
||||||
let font: UIFont
|
let font: UIFont
|
||||||
let textColor: UIColor
|
let textColor: UIColor
|
||||||
|
|
||||||
|
@ -82,6 +88,12 @@ extension MetaLabel {
|
||||||
case .accountListUsername:
|
case .accountListUsername:
|
||||||
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular), maximumPointSize: 20)
|
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular), maximumPointSize: 20)
|
||||||
textColor = Asset.Colors.Label.secondary.color
|
textColor = Asset.Colors.Label.secondary.color
|
||||||
|
case .sidebarHeadline(let isSelected):
|
||||||
|
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 22, weight: .regular), maximumPointSize: 20)
|
||||||
|
textColor = isSelected ? .white : Asset.Colors.Label.primary.color
|
||||||
|
case .sidebarSubheadline(let isSelected):
|
||||||
|
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 13, weight: .regular), maximumPointSize: 18)
|
||||||
|
textColor = isSelected ? .white : Asset.Colors.Label.secondary.color
|
||||||
}
|
}
|
||||||
|
|
||||||
self.font = font
|
self.font = font
|
||||||
|
|
|
@ -126,6 +126,7 @@ internal enum Asset {
|
||||||
internal static let profileFieldCollectionViewBackground = ColorAsset(name: "Theme/Mastodon/profile.field.collection.view.background")
|
internal static let profileFieldCollectionViewBackground = ColorAsset(name: "Theme/Mastodon/profile.field.collection.view.background")
|
||||||
internal static let secondaryGroupedSystemBackground = ColorAsset(name: "Theme/Mastodon/secondary.grouped.system.background")
|
internal static let secondaryGroupedSystemBackground = ColorAsset(name: "Theme/Mastodon/secondary.grouped.system.background")
|
||||||
internal static let secondarySystemBackground = ColorAsset(name: "Theme/Mastodon/secondary.system.background")
|
internal static let secondarySystemBackground = ColorAsset(name: "Theme/Mastodon/secondary.system.background")
|
||||||
|
internal static let sidebarBackground = ColorAsset(name: "Theme/Mastodon/sidebar.background")
|
||||||
internal static let systemBackground = ColorAsset(name: "Theme/Mastodon/system.background")
|
internal static let systemBackground = ColorAsset(name: "Theme/Mastodon/system.background")
|
||||||
internal static let systemElevatedBackground = ColorAsset(name: "Theme/Mastodon/system.elevated.background")
|
internal static let systemElevatedBackground = ColorAsset(name: "Theme/Mastodon/system.elevated.background")
|
||||||
internal static let systemGroupedBackground = ColorAsset(name: "Theme/Mastodon/system.grouped.background")
|
internal static let systemGroupedBackground = ColorAsset(name: "Theme/Mastodon/system.grouped.background")
|
||||||
|
@ -145,6 +146,7 @@ internal enum Asset {
|
||||||
internal static let profileFieldCollectionViewBackground = ColorAsset(name: "Theme/system/profile.field.collection.view.background")
|
internal static let profileFieldCollectionViewBackground = ColorAsset(name: "Theme/system/profile.field.collection.view.background")
|
||||||
internal static let secondaryGroupedSystemBackground = ColorAsset(name: "Theme/system/secondary.grouped.system.background")
|
internal static let secondaryGroupedSystemBackground = ColorAsset(name: "Theme/system/secondary.grouped.system.background")
|
||||||
internal static let secondarySystemBackground = ColorAsset(name: "Theme/system/secondary.system.background")
|
internal static let secondarySystemBackground = ColorAsset(name: "Theme/system/secondary.system.background")
|
||||||
|
internal static let sidebarBackground = ColorAsset(name: "Theme/system/sidebar.background")
|
||||||
internal static let systemBackground = ColorAsset(name: "Theme/system/system.background")
|
internal static let systemBackground = ColorAsset(name: "Theme/system/system.background")
|
||||||
internal static let systemElevatedBackground = ColorAsset(name: "Theme/system/system.elevated.background")
|
internal static let systemElevatedBackground = ColorAsset(name: "Theme/system/system.elevated.background")
|
||||||
internal static let systemGroupedBackground = ColorAsset(name: "Theme/system/system.grouped.background")
|
internal static let systemGroupedBackground = ColorAsset(name: "Theme/system/system.grouped.background")
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDuration</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xF1",
|
||||||
|
"green" : "0xF1",
|
||||||
|
"red" : "0xF1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.945",
|
||||||
|
"green" : "0.945",
|
||||||
|
"red" : "0.945"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
|
@ -924,8 +924,12 @@ extension ComposeViewController: UICollectionViewDelegate {
|
||||||
extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
|
extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
|
||||||
|
|
||||||
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
||||||
|
switch traitCollection.horizontalSizeClass {
|
||||||
|
case .compact:
|
||||||
return .overFullScreen
|
return .overFullScreen
|
||||||
//return traitCollection.userInterfaceIdiom == .pad ? .formSheet : .automatic
|
default:
|
||||||
|
return .pageSheet
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
||||||
|
|
|
@ -95,7 +95,13 @@ extension HomeTimelineViewController {
|
||||||
self.view.backgroundColor = theme.secondarySystemBackgroundColor
|
self.view.backgroundColor = theme.secondarySystemBackgroundColor
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
// navigationItem.leftBarButtonItem = settingBarButtonItem
|
viewModel.displaySettingBarButtonItem
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] displaySettingBarButtonItem in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.navigationItem.leftBarButtonItem = displaySettingBarButtonItem ? self.settingBarButtonItem : nil
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
navigationItem.titleView = titleView
|
navigationItem.titleView = titleView
|
||||||
titleView.delegate = self
|
titleView.delegate = self
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ final class HomeTimelineViewModel: NSObject {
|
||||||
let loadMiddleSateMachineList = CurrentValueSubject<[NSManagedObjectID: GKStateMachine], Never>([:]) // TimelineIndex.objectID : middle loading state machine
|
let loadMiddleSateMachineList = CurrentValueSubject<[NSManagedObjectID: GKStateMachine], Never>([:]) // TimelineIndex.objectID : middle loading state machine
|
||||||
var diffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>?
|
var diffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>?
|
||||||
var cellFrameCache = NSCache<NSNumber, NSValue>()
|
var cellFrameCache = NSCache<NSNumber, NSValue>()
|
||||||
|
let displaySettingBarButtonItem = CurrentValueSubject<Bool, Never>(true)
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
|
@ -63,6 +63,15 @@ class MainTabBarController: UITabBarController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sidebarImage: UIImage {
|
||||||
|
switch self {
|
||||||
|
case .home: return UIImage(systemName: "house")!
|
||||||
|
case .search: return UIImage(systemName: "magnifyingglass")!
|
||||||
|
case .notification: return UIImage(systemName: "bell")!
|
||||||
|
case .me: return UIImage(systemName: "person.fill")!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func viewController(context: AppContext, coordinator: SceneCoordinator) -> UIViewController {
|
func viewController(context: AppContext, coordinator: SceneCoordinator) -> UIViewController {
|
||||||
let viewController: UIViewController
|
let viewController: UIViewController
|
||||||
switch self {
|
switch self {
|
||||||
|
|
|
@ -27,9 +27,19 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency {
|
||||||
|
|
||||||
var currentSupplementaryTab: MainTabBarController.Tab = .home
|
var currentSupplementaryTab: MainTabBarController.Tab = .home
|
||||||
private(set) lazy var supplementaryViewControllers: [UIViewController] = {
|
private(set) lazy var supplementaryViewControllers: [UIViewController] = {
|
||||||
return MainTabBarController.Tab.allCases.map { tab in
|
let viewControllers = MainTabBarController.Tab.allCases.map { tab in
|
||||||
tab.viewController(context: context, coordinator: coordinator)
|
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)
|
private(set) lazy var mainTabBarController = MainTabBarController(context: context, coordinator: coordinator)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// Created by Cirno MainasuK on 2021-9-22.
|
// Created by Cirno MainasuK on 2021-9-22.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
@ -23,9 +24,16 @@ final class SidebarViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
weak var delegate: SidebarViewControllerDelegate?
|
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 {
|
static func createLayout() -> UICollectionViewLayout {
|
||||||
let layout = UICollectionViewCompositionalLayout() { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
|
let layout = UICollectionViewCompositionalLayout() { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
|
||||||
var configuration = UICollectionLayoutListConfiguration(appearance: .plain)
|
var configuration = UICollectionLayoutListConfiguration(appearance: .sidebar)
|
||||||
configuration.showsSeparators = false
|
configuration.showsSeparators = false
|
||||||
let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
|
let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
|
||||||
return section
|
return section
|
||||||
|
@ -46,14 +54,27 @@ extension SidebarViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
navigationItem.title = "Title"
|
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.title = domain
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
navigationItem.rightBarButtonItem = settingBarButtonItem
|
||||||
|
settingBarButtonItem.target = self
|
||||||
|
settingBarButtonItem.action = #selector(SidebarViewController.settingBarButtonItemPressed(_:))
|
||||||
navigationController?.navigationBar.prefersLargeTitles = true
|
navigationController?.navigationBar.prefersLargeTitles = true
|
||||||
|
|
||||||
let barAppearance = UINavigationBarAppearance()
|
setupBackground(theme: ThemeService.shared.currentTheme.value)
|
||||||
barAppearance.configureWithTransparentBackground()
|
ThemeService.shared.currentTheme
|
||||||
navigationItem.standardAppearance = barAppearance
|
.receive(on: DispatchQueue.main)
|
||||||
navigationItem.compactAppearance = barAppearance
|
.sink { [weak self] theme in
|
||||||
navigationItem.scrollEdgeAppearance = barAppearance
|
guard let self = self else { return }
|
||||||
|
self.setupBackground(theme: theme)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(collectionView)
|
view.addSubview(collectionView)
|
||||||
|
@ -68,6 +89,28 @@ extension SidebarViewController {
|
||||||
viewModel.setupDiffableDataSource(collectionView: collectionView)
|
viewModel.setupDiffableDataSource(collectionView: collectionView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupBackground(theme: Theme) {
|
||||||
|
let barAppearance = UINavigationBarAppearance()
|
||||||
|
barAppearance.configureWithOpaqueBackground()
|
||||||
|
barAppearance.backgroundColor = theme.sidebarBackgroundColor
|
||||||
|
barAppearance.shadowColor = .clear
|
||||||
|
barAppearance.shadowImage = UIImage() // remove separator line
|
||||||
|
navigationItem.standardAppearance = barAppearance
|
||||||
|
navigationItem.compactAppearance = barAppearance
|
||||||
|
navigationItem.scrollEdgeAppearance = barAppearance
|
||||||
|
|
||||||
|
view.backgroundColor = theme.sidebarBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// MARK: - UICollectionViewDelegate
|
||||||
|
|
|
@ -9,6 +9,8 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import Meta
|
||||||
|
import MastodonMeta
|
||||||
|
|
||||||
final class SidebarViewModel {
|
final class SidebarViewModel {
|
||||||
|
|
||||||
|
@ -57,28 +59,61 @@ extension SidebarViewModel {
|
||||||
func setupDiffableDataSource(
|
func setupDiffableDataSource(
|
||||||
collectionView: UICollectionView
|
collectionView: UICollectionView
|
||||||
) {
|
) {
|
||||||
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, MainTabBarController.Tab> { (cell, indexPath, item) in
|
let tabCellRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, MainTabBarController.Tab> { (cell, indexPath, item) in
|
||||||
var content = cell.defaultContentConfiguration()
|
let imageURL: URL? = {
|
||||||
content.text = item.title
|
switch item {
|
||||||
content.image = item.image
|
case .me:
|
||||||
cell.contentConfiguration = content
|
let authentication = self.context.authenticationService.activeMastodonAuthentication.value
|
||||||
cell.accessories = []
|
return authentication?.user.avatarImageURL()
|
||||||
|
default:
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
cell.item = SidebarListContentView.Item(
|
||||||
|
image: item.sidebarImage,
|
||||||
|
imageURL: imageURL,
|
||||||
|
headline: headline,
|
||||||
|
subheadline: nil
|
||||||
|
)
|
||||||
|
cell.setNeedsUpdateConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
let headerRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, HeaderViewModel> { (cell, indexPath, item) in
|
let headerRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, HeaderViewModel> { (cell, indexPath, item) in
|
||||||
var content = cell.defaultContentConfiguration()
|
var content = UIListContentConfiguration.sidebarHeader()
|
||||||
content.text = item.title
|
content.text = item.title
|
||||||
cell.contentConfiguration = content
|
cell.contentConfiguration = content
|
||||||
cell.accessories = [.outlineDisclosure()]
|
cell.accessories = [.outlineDisclosure()]
|
||||||
}
|
}
|
||||||
|
|
||||||
let accountRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, AccountViewModel> { (cell, indexPath, item) in
|
let accountRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, AccountViewModel> { (cell, indexPath, item) in
|
||||||
var content = cell.defaultContentConfiguration()
|
|
||||||
let authentication = AppContext.shared.managedObjectContext.object(with: item.authenticationObjectID) as! MastodonAuthentication
|
let authentication = AppContext.shared.managedObjectContext.object(with: item.authenticationObjectID) as! MastodonAuthentication
|
||||||
content.text = authentication.user.acctWithDomain
|
let user = authentication.user
|
||||||
content.image = nil
|
let imageURL = user.avatarImageURL()
|
||||||
cell.contentConfiguration = content
|
let headline: MetaContent = {
|
||||||
cell.accessories = []
|
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)
|
||||||
|
)
|
||||||
|
cell.setNeedsUpdateConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
let addAccountRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, AddAccountViewModel> { (cell, indexPath, item) in
|
let addAccountRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, AddAccountViewModel> { (cell, indexPath, item) in
|
||||||
|
@ -92,7 +127,7 @@ extension SidebarViewModel {
|
||||||
diffableDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in
|
diffableDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in
|
||||||
switch item {
|
switch item {
|
||||||
case .tab(let tab):
|
case .tab(let tab):
|
||||||
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: tab)
|
return collectionView.dequeueConfiguredReusableCell(using: tabCellRegistration, for: indexPath, item: tab)
|
||||||
case .header(let viewModel):
|
case .header(let viewModel):
|
||||||
return collectionView.dequeueConfiguredReusableCell(using: headerRegistration, for: indexPath, item: viewModel)
|
return collectionView.dequeueConfiguredReusableCell(using: headerRegistration, for: indexPath, item: viewModel)
|
||||||
case .account(let viewModel):
|
case .account(let viewModel):
|
||||||
|
@ -133,16 +168,22 @@ extension SidebarViewModel {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] authentications in
|
.sink { [weak self] authentications in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
// tab
|
||||||
|
var snapshot = self.diffableDataSource.snapshot()
|
||||||
|
snapshot.reloadItems([.tab(.me)])
|
||||||
|
self.diffableDataSource.apply(snapshot)
|
||||||
|
|
||||||
|
// account
|
||||||
|
var accountSectionSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
||||||
let headerItem = Item.header(HeaderViewModel(title: "Accounts"))
|
let headerItem = Item.header(HeaderViewModel(title: "Accounts"))
|
||||||
sectionSnapshot.append([headerItem], to: nil)
|
accountSectionSnapshot.append([headerItem], to: nil)
|
||||||
let items = authentications.map { authentication in
|
let accountItems = authentications.map { authentication in
|
||||||
Item.account(AccountViewModel(authenticationObjectID: authentication.objectID))
|
Item.account(AccountViewModel(authenticationObjectID: authentication.objectID))
|
||||||
}
|
}
|
||||||
sectionSnapshot.append(items, to: headerItem)
|
accountSectionSnapshot.append(accountItems, to: headerItem)
|
||||||
sectionSnapshot.append([.addAccount], to: headerItem)
|
accountSectionSnapshot.append([.addAccount], to: headerItem)
|
||||||
sectionSnapshot.expand([headerItem])
|
accountSectionSnapshot.expand([headerItem])
|
||||||
self.diffableDataSource.apply(sectionSnapshot, to: .account)
|
self.diffableDataSource.apply(accountSectionSnapshot, to: .account)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
//
|
||||||
|
// SidebarListTableViewCell.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-9-24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class SidebarListCollectionViewCell: UICollectionViewListCell {
|
||||||
|
|
||||||
|
var item: SidebarListContentView.Item?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SidebarListCollectionViewCell {
|
||||||
|
private func _init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||||
|
var newConfiguration = SidebarListContentView.ContentConfiguration().updated(for: state)
|
||||||
|
newConfiguration.item = item
|
||||||
|
contentConfiguration = newConfiguration
|
||||||
|
|
||||||
|
var newBackgroundConfiguration = UIBackgroundConfiguration.listSidebarCell().updated(for: state)
|
||||||
|
// Customize the background color to use the tint color when the cell is highlighted or selected.
|
||||||
|
if state.isSelected || state.isHighlighted {
|
||||||
|
newBackgroundConfiguration.backgroundColor = Asset.Colors.brandBlue.color
|
||||||
|
}
|
||||||
|
if state.isHighlighted {
|
||||||
|
newBackgroundConfiguration.backgroundColorTransformer = .init { $0.withAlphaComponent(0.8) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
backgroundConfiguration = newBackgroundConfiguration
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
//
|
||||||
|
// SidebarListContentView.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-9-24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
import MetaTextKit
|
||||||
|
import FLAnimatedImage
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
private var currentConfiguration: ContentConfiguration!
|
||||||
|
var configuration: UIContentConfiguration {
|
||||||
|
get {
|
||||||
|
currentConfiguration
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
guard let newConfiguration = newValue as? ContentConfiguration else { return }
|
||||||
|
apply(configuration: newConfiguration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(configuration: ContentConfiguration) {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
_init()
|
||||||
|
apply(configuration: configuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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),
|
||||||
|
])
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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),
|
||||||
|
])
|
||||||
|
|
||||||
|
animationImageView.isUserInteractionEnabled = false
|
||||||
|
headlineLabel.isUserInteractionEnabled = false
|
||||||
|
subheadlineLabel.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
imageView.contentMode = .scaleAspectFit
|
||||||
|
animationImageView.contentMode = .scaleAspectFit
|
||||||
|
imageView.tintColor = Asset.Colors.brandBlue.color
|
||||||
|
animationImageView.tintColor = Asset.Colors.brandBlue.color
|
||||||
|
}
|
||||||
|
|
||||||
|
private func apply(configuration: ContentConfiguration) {
|
||||||
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||||
|
|
||||||
|
guard currentConfiguration != configuration else { return }
|
||||||
|
currentConfiguration = configuration
|
||||||
|
|
||||||
|
guard let item = configuration.item else { return }
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
|
||||||
|
// configure model
|
||||||
|
imageView.isHidden = item.imageURL != nil
|
||||||
|
animationImageView.isHidden = item.imageURL == nil
|
||||||
|
imageView.image = item.image.withRenderingMode(.alwaysTemplate)
|
||||||
|
animationImageView.setImage(
|
||||||
|
url: item.imageURL,
|
||||||
|
placeholder: animationImageView.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SidebarListContentView {
|
||||||
|
struct Item: Hashable {
|
||||||
|
// state
|
||||||
|
var isSelected: Bool = false
|
||||||
|
|
||||||
|
// model
|
||||||
|
let image: UIImage
|
||||||
|
let imageURL: URL?
|
||||||
|
let headline: MetaContent
|
||||||
|
let subheadline: MetaContent?
|
||||||
|
|
||||||
|
static func == (lhs: SidebarListContentView.Item, rhs: SidebarListContentView.Item) -> Bool {
|
||||||
|
return lhs.isSelected == rhs.isSelected
|
||||||
|
&& 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(image)
|
||||||
|
imageURL.flatMap { hasher.combine($0) }
|
||||||
|
hasher.combine(headline.string)
|
||||||
|
subheadline.flatMap { hasher.combine($0.string) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentConfiguration: UIContentConfiguration, Hashable {
|
||||||
|
let logger = Logger(subsystem: "SidebarListContentView.ContentConfiguration", category: "ContentConfiguration")
|
||||||
|
|
||||||
|
var item: Item?
|
||||||
|
|
||||||
|
func makeContentView() -> UIView & UIContentView {
|
||||||
|
SidebarListContentView(configuration: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updated(for state: UIConfigurationState) -> ContentConfiguration {
|
||||||
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||||
|
|
||||||
|
var updatedConfiguration = self
|
||||||
|
|
||||||
|
if let state = state as? UICellConfigurationState {
|
||||||
|
updatedConfiguration.item?.isSelected = state.isHighlighted || state.isSelected
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
updatedConfiguration.item?.isSelected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (
|
||||||
|
lhs: ContentConfiguration,
|
||||||
|
rhs: ContentConfiguration
|
||||||
|
) -> Bool {
|
||||||
|
return lhs.item == rhs.item
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ struct MastodonTheme: Theme {
|
||||||
|
|
||||||
let navigationBarBackgroundColor = Asset.Theme.Mastodon.navigationBarBackground.color
|
let navigationBarBackgroundColor = Asset.Theme.Mastodon.navigationBarBackground.color
|
||||||
|
|
||||||
|
let sidebarBackgroundColor = Asset.Theme.Mastodon.sidebarBackground.color
|
||||||
|
|
||||||
let tabBarBackgroundColor = Asset.Theme.Mastodon.tabBarBackground.color
|
let tabBarBackgroundColor = Asset.Theme.Mastodon.tabBarBackground.color
|
||||||
let tabBarItemSelectedIconColor = Asset.Colors.brandBlue.color
|
let tabBarItemSelectedIconColor = Asset.Colors.brandBlue.color
|
||||||
let tabBarItemFocusedIconColor = Asset.Theme.Mastodon.tabBarItemInactiveIconColor.color
|
let tabBarItemFocusedIconColor = Asset.Theme.Mastodon.tabBarItemInactiveIconColor.color
|
||||||
|
|
|
@ -23,6 +23,8 @@ struct SystemTheme: Theme {
|
||||||
|
|
||||||
let navigationBarBackgroundColor = Asset.Theme.System.navigationBarBackground.color
|
let navigationBarBackgroundColor = Asset.Theme.System.navigationBarBackground.color
|
||||||
|
|
||||||
|
let sidebarBackgroundColor = Asset.Theme.Mastodon.sidebarBackground.color
|
||||||
|
|
||||||
let tabBarBackgroundColor = Asset.Theme.System.tabBarBackground.color
|
let tabBarBackgroundColor = Asset.Theme.System.tabBarBackground.color
|
||||||
let tabBarItemSelectedIconColor = Asset.Colors.brandBlue.color
|
let tabBarItemSelectedIconColor = Asset.Colors.brandBlue.color
|
||||||
let tabBarItemFocusedIconColor = Asset.Theme.System.tabBarItemInactiveIconColor.color
|
let tabBarItemFocusedIconColor = Asset.Theme.System.tabBarItemInactiveIconColor.color
|
||||||
|
|
|
@ -23,6 +23,8 @@ public protocol Theme {
|
||||||
|
|
||||||
var navigationBarBackgroundColor: UIColor { get }
|
var navigationBarBackgroundColor: UIColor { get }
|
||||||
|
|
||||||
|
var sidebarBackgroundColor: UIColor { get }
|
||||||
|
|
||||||
var tabBarBackgroundColor: UIColor { get }
|
var tabBarBackgroundColor: UIColor { get }
|
||||||
var tabBarItemSelectedIconColor: UIColor { get }
|
var tabBarItemSelectedIconColor: UIColor { get }
|
||||||
var tabBarItemFocusedIconColor: UIColor { get }
|
var tabBarItemFocusedIconColor: UIColor { get }
|
||||||
|
|
Loading…
Reference in New Issue