mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-21 15:50:59 +00:00
wip
This commit is contained in:
parent
6dd991f086
commit
02747215c5
12 changed files with 514 additions and 42 deletions
|
@ -18,7 +18,7 @@ public extension Timeline {
|
|||
typealias Id = String
|
||||
|
||||
static let unauthenticatedDefaults: [Timeline] = [.local, .federated]
|
||||
static let authenticatedDefaults: [Timeline] = [.home, .local, .federated, .favorites, .bookmarks]
|
||||
static let authenticatedDefaults: [Timeline] = [.home, .local, .federated]
|
||||
|
||||
var filterContext: Filter.Context? {
|
||||
switch self {
|
||||
|
|
33
Extensions/NavigationViewModel+Extensions.swift
Normal file
33
Extensions/NavigationViewModel+Extensions.swift
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
extension NavigationViewModel.Tab {
|
||||
var title: String {
|
||||
switch self {
|
||||
case .timelines:
|
||||
return NSLocalizedString("main-navigation.timelines", comment: "")
|
||||
case .explore:
|
||||
return NSLocalizedString("main-navigation.explore", comment: "")
|
||||
case .notifications:
|
||||
return NSLocalizedString("main-navigation.notifications", comment: "")
|
||||
case .messages:
|
||||
return NSLocalizedString("main-navigation.conversations", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
var systemImageName: String {
|
||||
switch self {
|
||||
case .timelines: return "newspaper"
|
||||
case .explore: return "magnifyingglass"
|
||||
case .notifications: return "bell"
|
||||
case .messages: return "envelope"
|
||||
}
|
||||
}
|
||||
|
||||
var tabBarItem: UITabBarItem {
|
||||
UITabBarItem(title: title, image: UIImage(systemName: systemImageName), selectedImage: nil)
|
||||
}
|
||||
}
|
|
@ -88,6 +88,10 @@
|
|||
"identities.pending" = "Pending";
|
||||
"lists.new-list-title" = "New List Title";
|
||||
"load-more" = "Load More";
|
||||
"main-navigation.timelines" = "Timelines";
|
||||
"main-navigation.explore" = "Explore";
|
||||
"main-navigation.notifications" = "Notifications";
|
||||
"main-navigation.conversations" = "Conversations";
|
||||
"messages" = "Messages";
|
||||
"ok" = "OK";
|
||||
"pending.pending-confirmation" = "Your account is pending confirmation";
|
||||
|
@ -201,5 +205,8 @@
|
|||
"status.visibility.direct.description" = "Visible for mentioned users only";
|
||||
"submit" = "Submit";
|
||||
"timelines.home" = "Home";
|
||||
"timelines.home.description" = "Posts from accounts you're following";
|
||||
"timelines.local" = "Local";
|
||||
"timelines.local.description-%@" = "Public posts on %@";
|
||||
"timelines.federated" = "Federated";
|
||||
"timelines.federated.description-%@" = "Public posts on instances known by %@";
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
|
||||
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
|
||||
D02E1F95250B13210071AD56 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02E1F94250B13210071AD56 /* SafariView.swift */; };
|
||||
D035F86925B7F2ED00DC75ED /* MainNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F86825B7F2ED00DC75ED /* MainNavigationViewController.swift */; };
|
||||
D035F86F25B7F30E00DC75ED /* MainNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F86E25B7F30E00DC75ED /* MainNavigationView.swift */; };
|
||||
D035F87D25B7F61600DC75ED /* TimelinesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F87C25B7F61600DC75ED /* TimelinesViewController.swift */; };
|
||||
D035F88725B8016000DC75ED /* NavigationViewModel+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F88625B8016000DC75ED /* NavigationViewModel+Extensions.swift */; };
|
||||
D035F89125B8067100DC75ED /* TimelinesTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035F89025B8067100DC75ED /* TimelinesTitleView.swift */; };
|
||||
D036AA02254B6101009094DF /* NotificationListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA01254B6101009094DF /* NotificationListCell.swift */; };
|
||||
D036AA07254B6118009094DF /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA06254B6118009094DF /* NotificationView.swift */; };
|
||||
D036AA0C254B612B009094DF /* NotificationContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */; };
|
||||
|
@ -204,6 +209,11 @@
|
|||
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
|
||||
D01F41E224F8889700D55A2D /* AttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsView.swift; sourceTree = "<group>"; };
|
||||
D02E1F94250B13210071AD56 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
||||
D035F86825B7F2ED00DC75ED /* MainNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationViewController.swift; sourceTree = "<group>"; };
|
||||
D035F86E25B7F30E00DC75ED /* MainNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationView.swift; sourceTree = "<group>"; };
|
||||
D035F87C25B7F61600DC75ED /* TimelinesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesViewController.swift; sourceTree = "<group>"; };
|
||||
D035F88625B8016000DC75ED /* NavigationViewModel+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationViewModel+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D035F89025B8067100DC75ED /* TimelinesTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesTitleView.swift; sourceTree = "<group>"; };
|
||||
D036AA01254B6101009094DF /* NotificationListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationListCell.swift; sourceTree = "<group>"; };
|
||||
D036AA06254B6118009094DF /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = "<group>"; };
|
||||
D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentConfiguration.swift; sourceTree = "<group>"; };
|
||||
|
@ -504,6 +514,7 @@
|
|||
D0B8510B25259E56004E0744 /* LoadMoreCell.swift */,
|
||||
D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */,
|
||||
D0E569DA2529319100FA1D72 /* LoadMoreView.swift */,
|
||||
D035F86E25B7F30E00DC75ED /* MainNavigationView.swift */,
|
||||
D05936FE25AA94EA00754FDF /* MarkAttachmentsSensitiveView.swift */,
|
||||
D03B1B29253818F3008F964B /* MediaPreferencesView.swift */,
|
||||
D0FCC10F259C4F20000B67DF /* NewStatusView.swift */,
|
||||
|
@ -526,6 +537,7 @@
|
|||
D0625E55250F086B00502611 /* Status */,
|
||||
D0C7D42524F76169001EBDBB /* TableView.swift */,
|
||||
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */,
|
||||
D035F89025B8067100DC75ED /* TimelinesTitleView.swift */,
|
||||
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */,
|
||||
D0EA59472522B8B600804347 /* ViewConstants.swift */,
|
||||
D0F2D54A2581CF7D00986197 /* VisualEffectBlur.swift */,
|
||||
|
@ -542,9 +554,11 @@
|
|||
D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */,
|
||||
D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */,
|
||||
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */,
|
||||
D035F86825B7F2ED00DC75ED /* MainNavigationViewController.swift */,
|
||||
D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */,
|
||||
D06BC5E525202AD90079541D /* ProfileViewController.swift */,
|
||||
D0F0B12D251A97E400942152 /* TableViewController.swift */,
|
||||
D035F87C25B7F61600DC75ED /* TimelinesViewController.swift */,
|
||||
);
|
||||
path = "View Controllers";
|
||||
sourceTree = "<group>";
|
||||
|
@ -574,6 +588,7 @@
|
|||
D05E688425B55AE8001FB2C6 /* AVURLAsset+Extensions.swift */,
|
||||
D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */,
|
||||
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
||||
D035F88625B8016000DC75ED /* NavigationViewModel+Extensions.swift */,
|
||||
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
||||
D07EC7CE25B13921006DF726 /* PickerEmoji+Extensions.swift */,
|
||||
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */,
|
||||
|
@ -856,6 +871,7 @@
|
|||
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */,
|
||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
||||
D07EC81125B232C2006DF726 /* SystemEmoji+Extensions.swift in Sources */,
|
||||
D035F87D25B7F61600DC75ED /* TimelinesViewController.swift in Sources */,
|
||||
D059373325AAEA7000754FDF /* CompositionPollView.swift in Sources */,
|
||||
D08B8D8D2544E6EC00B1EBEF /* PollResultView.swift in Sources */,
|
||||
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
|
||||
|
@ -874,7 +890,9 @@
|
|||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
|
||||
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */,
|
||||
D059373E25AB8D5200754FDF /* CompositionPollOptionView.swift in Sources */,
|
||||
D035F89125B8067100DC75ED /* TimelinesTitleView.swift in Sources */,
|
||||
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */,
|
||||
D035F86F25B7F30E00DC75ED /* MainNavigationView.swift in Sources */,
|
||||
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */,
|
||||
D05936F425AA66A600754FDF /* UIView+Extensions.swift in Sources */,
|
||||
D05936E925AA3F3D00754FDF /* EditAttachmentView.swift in Sources */,
|
||||
|
@ -883,6 +901,7 @@
|
|||
D036AA0C254B612B009094DF /* NotificationContentConfiguration.swift in Sources */,
|
||||
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
|
||||
D08B8D3D253F929E00B1EBEF /* ImageViewController.swift in Sources */,
|
||||
D035F86925B7F2ED00DC75ED /* MainNavigationViewController.swift in Sources */,
|
||||
D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */,
|
||||
D08E52612579D2E100FA2C5F /* DomainBlocksView.swift in Sources */,
|
||||
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */,
|
||||
|
@ -901,6 +920,7 @@
|
|||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
||||
D035F88725B8016000DC75ED /* NavigationViewModel+Extensions.swift in Sources */,
|
||||
D0FCC110259C4F20000B67DF /* NewStatusView.swift in Sources */,
|
||||
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
||||
D088406D25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */,
|
||||
|
|
64
View Controllers/MainNavigationViewController.swift
Normal file
64
View Controllers/MainNavigationViewController.swift
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class MainNavigationViewController: UITabBarController {
|
||||
private let viewModel: NavigationViewModel
|
||||
private let rootViewModel: RootViewModel
|
||||
|
||||
init(viewModel: NavigationViewModel, rootViewModel: RootViewModel) {
|
||||
self.viewModel = viewModel
|
||||
self.rootViewModel = rootViewModel
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let timelinesViewController = TimelinesViewController(
|
||||
viewModel: viewModel,
|
||||
rootViewModel: rootViewModel)
|
||||
let timelinesNavigationController = UINavigationController(rootViewController: timelinesViewController)
|
||||
|
||||
if let notificationsViewModel = viewModel.notificationsViewModel,
|
||||
let conversationsViewModel = viewModel.conversationsViewModel {
|
||||
let notificationsViewController = TableViewController(
|
||||
viewModel: notificationsViewModel,
|
||||
rootViewModel: rootViewModel,
|
||||
identification: viewModel.identification)
|
||||
|
||||
notificationsViewController.tabBarItem = NavigationViewModel.Tab.notifications.tabBarItem
|
||||
|
||||
let notificationsNavigationViewController = UINavigationController(
|
||||
rootViewController: notificationsViewController)
|
||||
|
||||
let conversationsViewController = TableViewController(
|
||||
viewModel: conversationsViewModel,
|
||||
rootViewModel: rootViewModel,
|
||||
identification: viewModel.identification)
|
||||
|
||||
conversationsViewController.tabBarItem = NavigationViewModel.Tab.messages.tabBarItem
|
||||
conversationsViewController.navigationItem.title = NavigationViewModel.Tab.messages.title
|
||||
|
||||
let conversationsNavigationViewController = UINavigationController(
|
||||
rootViewController: conversationsViewController)
|
||||
|
||||
viewControllers = [
|
||||
timelinesNavigationController,
|
||||
notificationsNavigationViewController,
|
||||
conversationsNavigationViewController
|
||||
]
|
||||
} else {
|
||||
viewControllers = [
|
||||
timelinesNavigationController
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
131
View Controllers/TimelinesViewController.swift
Normal file
131
View Controllers/TimelinesViewController.swift
Normal file
|
@ -0,0 +1,131 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class TimelinesViewController: UIPageViewController {
|
||||
private let titleView: TimelinesTitleView
|
||||
private let timelineViewControllers: [TableViewController]
|
||||
private let viewModel: NavigationViewModel
|
||||
private let rootViewModel: RootViewModel
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(viewModel: NavigationViewModel, rootViewModel: RootViewModel) {
|
||||
self.viewModel = viewModel
|
||||
self.rootViewModel = rootViewModel
|
||||
|
||||
let timelineViewModels: [CollectionViewModel]
|
||||
|
||||
if let homeTimelineViewModel = viewModel.homeTimelineViewModel {
|
||||
timelineViewModels = [
|
||||
homeTimelineViewModel,
|
||||
viewModel.localTimelineViewModel,
|
||||
viewModel.federatedTimelineViewModel]
|
||||
} else {
|
||||
timelineViewModels = [
|
||||
viewModel.localTimelineViewModel,
|
||||
viewModel.federatedTimelineViewModel]
|
||||
}
|
||||
|
||||
titleView = TimelinesTitleView(
|
||||
timelines: viewModel.identification.identity.authenticated
|
||||
? Timeline.authenticatedDefaults
|
||||
: Timeline.unauthenticatedDefaults,
|
||||
identification: viewModel.identification)
|
||||
|
||||
timelineViewControllers = timelineViewModels.map {
|
||||
TableViewController(
|
||||
viewModel: $0,
|
||||
rootViewModel: rootViewModel,
|
||||
identification: viewModel.identification)
|
||||
}
|
||||
|
||||
super.init(transitionStyle: .scroll,
|
||||
navigationOrientation: .horizontal,
|
||||
options: [.interPageSpacing: CGFloat.defaultSpacing])
|
||||
|
||||
if let firstViewController = timelineViewControllers.first {
|
||||
setViewControllers([firstViewController], direction: .forward, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
dataSource = self
|
||||
delegate = self
|
||||
|
||||
tabBarItem = UITabBarItem(
|
||||
title: NSLocalizedString("main-navigation.timelines", comment: ""),
|
||||
image: UIImage(systemName: "newspaper"),
|
||||
selectedImage: nil)
|
||||
|
||||
navigationItem.titleView = titleView
|
||||
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .close)
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "megaphone"), primaryAction: nil)
|
||||
|
||||
titleView.$selectedTimeline
|
||||
.compactMap { [weak self] in self?.titleView.timelines.firstIndex(of: $0) }
|
||||
.sink { [weak self] index in
|
||||
guard let self = self,
|
||||
let currentViewController = self.viewControllers?.first as? TableViewController,
|
||||
let currentIndex = self.timelineViewControllers.firstIndex(of: currentViewController),
|
||||
index != currentIndex
|
||||
else { return }
|
||||
|
||||
self.setViewControllers(
|
||||
[self.timelineViewControllers[index]],
|
||||
direction: index > currentIndex ? .forward : .reverse,
|
||||
animated: !UIAccessibility.isReduceMotionEnabled)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
extension TimelinesViewController: UIPageViewControllerDataSource {
|
||||
func pageViewController(_ pageViewController: UIPageViewController,
|
||||
viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||
guard
|
||||
let timelineViewController = viewController as? TableViewController,
|
||||
let index = timelineViewControllers.firstIndex(of: timelineViewController),
|
||||
index + 1 < timelineViewControllers.count
|
||||
else { return nil }
|
||||
|
||||
return timelineViewControllers[index + 1]
|
||||
}
|
||||
|
||||
func pageViewController(_ pageViewController: UIPageViewController,
|
||||
viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
||||
guard
|
||||
let timelineViewController = viewController as? TableViewController,
|
||||
let index = timelineViewControllers.firstIndex(of: timelineViewController),
|
||||
index > 0
|
||||
else { return nil }
|
||||
|
||||
return timelineViewControllers[index - 1]
|
||||
}
|
||||
}
|
||||
|
||||
extension TimelinesViewController: UIPageViewControllerDelegate {
|
||||
func pageViewController(_ pageViewController: UIPageViewController,
|
||||
didFinishAnimating finished: Bool,
|
||||
previousViewControllers: [UIViewController],
|
||||
transitionCompleted completed: Bool) {
|
||||
guard let viewController = viewControllers?.first as? TableViewController,
|
||||
let index = timelineViewControllers.firstIndex(of: viewController)
|
||||
else { return }
|
||||
|
||||
let timeline = titleView.timelines[index]
|
||||
|
||||
if titleView.selectedTimeline != timeline {
|
||||
titleView.selectedTimeline = timeline
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,38 +21,56 @@ public final class NavigationViewModel: ObservableObject {
|
|||
@Published public var alertItem: AlertItem?
|
||||
public private(set) var timelineViewModel: CollectionItemsViewModel
|
||||
|
||||
public var notificationsViewModel: CollectionViewModel? {
|
||||
public lazy var homeTimelineViewModel: CollectionViewModel? = {
|
||||
if identification.identity.authenticated {
|
||||
if _notificationsViewModel == nil {
|
||||
_notificationsViewModel = CollectionItemsViewModel(
|
||||
return CollectionItemsViewModel(
|
||||
collectionService: identification.service.service(timeline: .home),
|
||||
identification: identification)
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
public lazy var localTimelineViewModel: CollectionViewModel = {
|
||||
CollectionItemsViewModel(
|
||||
collectionService: identification.service.service(timeline: .local),
|
||||
identification: identification)
|
||||
}()
|
||||
|
||||
public lazy var federatedTimelineViewModel: CollectionViewModel = {
|
||||
CollectionItemsViewModel(
|
||||
collectionService: identification.service.service(timeline: .federated),
|
||||
identification: identification)
|
||||
}()
|
||||
|
||||
public lazy var notificationsViewModel: CollectionViewModel? = {
|
||||
if identification.identity.authenticated {
|
||||
let notificationsViewModel = CollectionItemsViewModel(
|
||||
collectionService: identification.service.notificationsService(),
|
||||
identification: identification)
|
||||
_notificationsViewModel?.request(maxId: nil, minId: nil)
|
||||
}
|
||||
|
||||
return _notificationsViewModel
|
||||
notificationsViewModel.request(maxId: nil, minId: nil)
|
||||
|
||||
return notificationsViewModel
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
public var conversationsViewModel: CollectionViewModel? {
|
||||
public lazy var conversationsViewModel: CollectionViewModel? = {
|
||||
if identification.identity.authenticated {
|
||||
if _conversationsViewModel == nil {
|
||||
_conversationsViewModel = CollectionItemsViewModel(
|
||||
let conversationsViewModel = CollectionItemsViewModel(
|
||||
collectionService: identification.service.conversationsService(),
|
||||
identification: identification)
|
||||
_conversationsViewModel?.request(maxId: nil, minId: nil)
|
||||
}
|
||||
|
||||
return _conversationsViewModel
|
||||
conversationsViewModel.request(maxId: nil, minId: nil)
|
||||
|
||||
return conversationsViewModel
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
private var _notificationsViewModel: CollectionViewModel?
|
||||
private var _conversationsViewModel: CollectionViewModel?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
public init(identification: Identification) {
|
||||
|
|
32
Views/MainNavigationView.swift
Normal file
32
Views/MainNavigationView.swift
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct MainNavigationView: UIViewControllerRepresentable {
|
||||
let viewModelClosure: () -> NavigationViewModel
|
||||
@EnvironmentObject var rootViewModel: RootViewModel
|
||||
@EnvironmentObject var identification: Identification
|
||||
|
||||
func makeUIViewController(context: Context) -> MainNavigationViewController {
|
||||
MainNavigationViewController(
|
||||
viewModel: viewModelClosure(),
|
||||
rootViewModel: rootViewModel)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: MainNavigationViewController, context: Context) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct MainNavigationView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MainNavigationView { NavigationViewModel(identification: .preview) }
|
||||
.environmentObject(Identification.preview)
|
||||
.environmentObject(RootViewModel.preview)
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -8,10 +8,11 @@ struct RootView: View {
|
|||
|
||||
var body: some View {
|
||||
if let navigationViewModel = viewModel.navigationViewModel {
|
||||
TabNavigationView(viewModel: navigationViewModel)
|
||||
MainNavigationView { navigationViewModel }
|
||||
.id(navigationViewModel.identification.identity.id)
|
||||
.environmentObject(viewModel)
|
||||
.transition(.opacity)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
} else {
|
||||
NavigationView {
|
||||
AddIdentityView(viewModel: viewModel.addIdentityViewModel())
|
||||
|
|
|
@ -188,7 +188,8 @@ private extension TabNavigationView {
|
|||
}
|
||||
}
|
||||
|
||||
private extension Timeline {
|
||||
// TODO: move
|
||||
extension Timeline {
|
||||
var title: String {
|
||||
switch self {
|
||||
case .home:
|
||||
|
@ -210,10 +211,40 @@ private extension Timeline {
|
|||
}
|
||||
}
|
||||
|
||||
func subtitle(identification: Identification) -> String? {
|
||||
switch self {
|
||||
case .home:
|
||||
return identification.identity.handle
|
||||
default:
|
||||
return identification.identity.instance?.uri
|
||||
}
|
||||
}
|
||||
|
||||
func description(instanceName: String?) -> String? {
|
||||
switch self {
|
||||
case .home:
|
||||
return NSLocalizedString("timelines.home.description", comment: "")
|
||||
case .local:
|
||||
guard let instanceName = instanceName else { return nil }
|
||||
|
||||
return String.localizedStringWithFormat(
|
||||
NSLocalizedString("timelines.local.description-%@", comment: ""),
|
||||
instanceName)
|
||||
case .federated:
|
||||
guard let instanceName = instanceName else { return nil }
|
||||
|
||||
return String.localizedStringWithFormat(
|
||||
NSLocalizedString("timelines.federated.description-%@", comment: ""),
|
||||
instanceName)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var systemImageName: String {
|
||||
switch self {
|
||||
case .home: return "house"
|
||||
case .local: return "person.3"
|
||||
case .local: return "building.2.crop.circle"
|
||||
case .federated: return "network"
|
||||
case .list: return "scroll"
|
||||
case .tag: return "number"
|
||||
|
@ -224,26 +255,6 @@ private extension Timeline {
|
|||
}
|
||||
}
|
||||
|
||||
extension NavigationViewModel.Tab {
|
||||
var title: String {
|
||||
switch self {
|
||||
case .timelines: return "Timelines"
|
||||
case .explore: return "Explore"
|
||||
case .notifications: return "Notifications"
|
||||
case .messages: return "Messages"
|
||||
}
|
||||
}
|
||||
|
||||
var systemImageName: String {
|
||||
switch self {
|
||||
case .timelines: return "newspaper"
|
||||
case .explore: return "magnifyingglass"
|
||||
case .notifications: return "bell"
|
||||
case .messages: return "envelope"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
|
|
153
Views/TimelinesTitleView.swift
Normal file
153
Views/TimelinesTitleView.swift
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class TimelinesTitleView: UIControl {
|
||||
let timelines: [Timeline]
|
||||
private let titleLabel = UILabel()
|
||||
private let subtitleLabel = UILabel()
|
||||
private let imageView = UIImageView()
|
||||
private let chevronImageView = UIImageView(image: TimelinesTitleView.closedImage)
|
||||
private let identification: Identification
|
||||
|
||||
@Published var selectedTimeline: Timeline {
|
||||
didSet { applyTimelineSelection() }
|
||||
}
|
||||
|
||||
init(timelines: [Timeline], identification: Identification) {
|
||||
self.timelines = timelines
|
||||
self.identification = identification
|
||||
|
||||
guard let timeline = timelines.first else {
|
||||
fatalError("TimelinesTitleView must be initialized with a non-empty timelines array")
|
||||
}
|
||||
|
||||
selectedTimeline = timeline
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
accessibilityTraits = .button
|
||||
isAccessibilityElement = true
|
||||
showsMenuAsPrimaryAction = true
|
||||
isContextMenuInteractionEnabled = true
|
||||
|
||||
addSubview(imageView)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||
imageView.tintColor = .label
|
||||
|
||||
addSubview(chevronImageView)
|
||||
chevronImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
chevronImageView.contentMode = .scaleAspectFit
|
||||
chevronImageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||
|
||||
addSubview(titleLabel)
|
||||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
titleLabel.adjustsFontForContentSizeCategory = true
|
||||
titleLabel.font = .preferredFont(forTextStyle: .headline)
|
||||
titleLabel.adjustsFontSizeToFitWidth = true
|
||||
titleLabel.minimumScaleFactor = 0.5
|
||||
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||
titleLabel.setContentHuggingPriority(.required, for: .vertical)
|
||||
titleLabel.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
|
||||
addSubview(subtitleLabel)
|
||||
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
subtitleLabel.adjustsFontForContentSizeCategory = true
|
||||
subtitleLabel.font = .preferredFont(forTextStyle: .caption2)
|
||||
subtitleLabel.adjustsFontSizeToFitWidth = true
|
||||
subtitleLabel.textAlignment = .center
|
||||
subtitleLabel.minimumScaleFactor = 0.5
|
||||
subtitleLabel.textColor = .secondaryLabel
|
||||
subtitleLabel.setContentHuggingPriority(.required, for: .vertical)
|
||||
subtitleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
subtitleLabel.setContentCompressionResistancePriority(.justBelowMax, for: .vertical)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor),
|
||||
imageView.topAnchor.constraint(equalTo: titleLabel.topAnchor),
|
||||
imageView.bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor),
|
||||
titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
titleLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: .compactSpacing),
|
||||
titleLabel.topAnchor.constraint(equalTo: topAnchor),
|
||||
chevronImageView.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: .defaultSpacing),
|
||||
chevronImageView.topAnchor.constraint(equalTo: titleLabel.topAnchor),
|
||||
chevronImageView.bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor),
|
||||
chevronImageView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor),
|
||||
subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
|
||||
subtitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
subtitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
subtitleLabel.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
|
||||
applyTimelineSelection()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet {
|
||||
alpha = isHighlighted ? Self.highlightedAlpha : 1
|
||||
}
|
||||
}
|
||||
|
||||
override func menuAttachmentPoint(for configuration: UIContextMenuConfiguration) -> CGPoint {
|
||||
CGPoint(x: (bounds.width - .systemMenuWidth) / 2 + .systemMenuInset, y: bounds.maxY + .compactSpacing)
|
||||
}
|
||||
|
||||
override func contextMenuInteraction(
|
||||
_ interaction: UIContextMenuInteraction,
|
||||
configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||
UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in
|
||||
guard let self = self else { return nil }
|
||||
|
||||
return UIMenu(children: self.timelines.map { timeline in
|
||||
UIAction(
|
||||
title: timeline.title,
|
||||
image: UIImage(systemName: timeline.systemImageName),
|
||||
attributes: timeline == self.selectedTimeline ? .disabled : [],
|
||||
state: timeline == self.selectedTimeline ? .on : .off) { _ in
|
||||
self.selectedTimeline = timeline
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func contextMenuInteraction(
|
||||
_ interaction: UIContextMenuInteraction,
|
||||
willDisplayMenuFor configuration: UIContextMenuConfiguration,
|
||||
animator: UIContextMenuInteractionAnimating?) {
|
||||
chevronImageView.image = Self.openImage
|
||||
}
|
||||
|
||||
override func contextMenuInteraction(
|
||||
_ interaction: UIContextMenuInteraction,
|
||||
willEndFor configuration: UIContextMenuConfiguration,
|
||||
animator: UIContextMenuInteractionAnimating?) {
|
||||
chevronImageView.image = Self.closedImage
|
||||
alpha = 1 // system bug
|
||||
}
|
||||
}
|
||||
|
||||
private extension TimelinesTitleView {
|
||||
static let highlightedAlpha: CGFloat = 0.5
|
||||
static let openImage = UIImage(
|
||||
systemName: "chevron.compact.up",
|
||||
withConfiguration: UIImage.SymbolConfiguration(scale: .small))
|
||||
static let closedImage = UIImage(
|
||||
systemName: "chevron.compact.down",
|
||||
withConfiguration: UIImage.SymbolConfiguration(scale: .small))
|
||||
func applyTimelineSelection() {
|
||||
imageView.image = UIImage(
|
||||
systemName: selectedTimeline.systemImageName,
|
||||
withConfiguration: UIImage.SymbolConfiguration(scale: .small))
|
||||
titleLabel.text = selectedTimeline.title
|
||||
subtitleLabel.text = selectedTimeline.subtitle(identification: identification)
|
||||
}
|
||||
}
|
|
@ -11,8 +11,10 @@ extension CGFloat {
|
|||
static let hairline = 1 / UIScreen.main.scale
|
||||
static let minimumButtonDimension: Self = 44
|
||||
static let barButtonItemDimension: Self = 28
|
||||
static let newStatusButtonDimension: CGFloat = 54
|
||||
static let defaultShadowRadius: CGFloat = 2
|
||||
static let newStatusButtonDimension: Self = 54
|
||||
static let defaultShadowRadius: Self = 2
|
||||
static let systemMenuWidth: Self = 250
|
||||
static let systemMenuInset: Self = 15
|
||||
}
|
||||
|
||||
extension CGRect {
|
||||
|
|
Loading…
Reference in a new issue