Bubble/Threaded/Data/Navigator.swift

234 lines
7.3 KiB
Swift
Raw Normal View History

2023-12-29 11:17:37 +01:00
//Made by Lumaa
import Foundation
import SwiftUI
@Observable
2024-02-16 17:26:32 +01:00
public class Navigator: ObservableObject {
2023-12-29 11:17:37 +01:00
public var path: [RouterDestination] = []
public var presentedSheet: SheetDestination?
public var presentedCover: SheetDestination?
2023-12-29 11:17:37 +01:00
public var selectedTab: TabDestination = .timeline
public func navigate(to: RouterDestination) {
path.append(to)
}
2024-01-28 17:51:50 +01:00
public func removeSettingsOfPath() {
self.path = self.path.filter({ !RouterDestination.allSettings.contains($0) })
}
2023-12-29 11:17:37 +01:00
}
2024-02-17 02:06:50 +01:00
public class UniversalNavigator: Navigator {
public var client: Client?
public func handle(url: URL) -> OpenURLAction.Result {
guard let client = self.client else { return .systemAction }
let path: String = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "") // remove all path
let urlPath: URL = URL(string: path)!
if client.isAuth && client.hasConnection(with: url) {
if urlPath.lastPathComponent.starts(with: "@") {
Task {
do {
let search: SearchResults = try await client.get(endpoint: Search.search(query: urlPath.lastPathComponent, type: "accounts", offset: nil, following: nil), forceVersion: .v2)
let acc: Account = search.accounts.first ?? .placeholder()
self.navigate(to: .account(acc: acc))
} catch {
print(error)
}
}
}
}
return .handled
}
}
2023-12-29 11:17:37 +01:00
public enum TabDestination: Identifiable {
case timeline
case search
case activity
case profile
public var id: String {
switch self {
case .timeline:
return "timeline"
case .search:
return "search"
case .activity:
return "activity"
case .profile:
return "profile"
}
}
}
public enum SheetDestination: Identifiable {
case welcome
2024-01-27 08:56:22 +01:00
case shop
2024-02-18 18:39:07 +01:00
case media(attachments: [MediaAttachment], selected: MediaAttachment)
2024-01-27 08:56:22 +01:00
2023-12-29 11:17:37 +01:00
case mastodonLogin(logged: Binding<Bool>)
case post(content: String = "", replyId: String? = nil, editId: String? = nil)
2024-01-21 13:08:06 +01:00
case safari(url: URL)
case shareImage(image: UIImage, status: Status)
2023-12-29 11:17:37 +01:00
public var id: String {
switch self {
case .welcome:
return "welcome"
2024-01-27 08:56:22 +01:00
case .shop:
return "shop"
2024-02-18 18:39:07 +01:00
case .media:
return "media"
2024-01-27 08:56:22 +01:00
2023-12-29 11:17:37 +01:00
case .mastodonLogin:
return "login"
case .post:
return "post"
2024-01-21 13:08:06 +01:00
case .safari:
return "safari"
2024-01-21 16:34:26 +01:00
case .shareImage:
return "shareImage"
2023-12-29 11:17:37 +01:00
}
}
public var isCover: Bool {
switch self {
case .welcome:
return true
2024-01-27 08:56:22 +01:00
case .shop:
return true
2024-02-18 18:39:07 +01:00
case .media:
return true
2023-12-29 11:17:37 +01:00
case .mastodonLogin:
return false
case .post:
return false
2024-01-21 13:08:06 +01:00
case .safari:
return false
2024-01-21 16:34:26 +01:00
case .shareImage:
return false
2023-12-29 11:17:37 +01:00
}
}
}
public enum RouterDestination: Hashable {
case settings
2024-02-12 02:42:56 +01:00
case support
case appearence
2023-12-29 11:17:37 +01:00
case account(acc: Account)
2024-01-10 17:45:41 +01:00
case post(status: Status)
case about
case contacts
case timeline(timeline: TimelineFilter?)
2023-12-29 11:17:37 +01:00
}
2024-01-28 17:51:50 +01:00
extension RouterDestination {
2024-02-12 02:42:56 +01:00
static let allSettings: [RouterDestination] = [.settings, .support, .about, .appearence]
2024-01-28 17:51:50 +01:00
}
2023-12-29 11:17:37 +01:00
extension View {
func withAppRouter(_ navigator: Navigator) -> some View {
2023-12-29 11:17:37 +01:00
navigationDestination(for: RouterDestination.self) { destination in
switch destination {
case .settings:
SettingsView(navigator: navigator)
2024-02-12 02:42:56 +01:00
case .support:
SupportView()
case .appearence:
AppearenceView()
2023-12-29 11:17:37 +01:00
case .account(let acc):
ProfileView(account: acc)
2024-01-10 17:45:41 +01:00
case .post(let status):
PostDetailsView(status: status)
case .about:
AboutView()
case .contacts:
ContactsView()
case .timeline(let timeline):
PostsView(filter: timeline ?? .home, showHero: false)
2023-12-29 11:17:37 +01:00
}
}
}
func withSheets(sheetDestination: Binding<SheetDestination?>) -> some View {
sheet(item: sheetDestination) { destination in
viewSheet(destination: destination)
2023-12-29 11:17:37 +01:00
}
}
func withCovers(sheetDestination: Binding<SheetDestination?>) -> some View {
fullScreenCover(item: sheetDestination) { destination in
viewCover(destination: destination)
2023-12-29 11:17:37 +01:00
}
}
private func viewCover(destination: SheetDestination) -> some View {
Group {
switch destination {
case .welcome:
ConnectView()
case .shop:
ShopView()
2024-02-18 18:39:07 +01:00
case .media(let attachments, let selected):
AttachmentView(attachments: attachments, selectedId: selected.id)
default:
EmptySheetView(destId: destination.id)
}
}
}
private func viewSheet(destination: SheetDestination) -> some View {
Group {
switch destination {
case .post(let content, let replyId, let editId):
NavigationStack {
PostingView(initialString: content, replyId: replyId, editId: editId)
.tint(Color(uiColor: UIColor.label))
}
case let .mastodonLogin(logged):
AddInstanceView(logged: logged)
.tint(Color.accentColor)
case let .safari(url):
SfSafariView(url: url)
.ignoresSafeArea()
case let .shareImage(image, status):
ShareSheet(image: image, status: status)
default:
EmptySheetView(destId: destination.id)
}
}
}
2023-12-29 11:17:37 +01:00
}
2024-01-27 08:56:22 +01:00
/// This view is visible when the `viewRepresentation(destination: SheetDestination)` doesn't support the given `SheetDestination`
2024-01-27 08:56:22 +01:00
private struct EmptySheetView: View {
@Environment(\.dismiss) private var dismiss
var destId: String = ""
2024-01-28 17:51:50 +01:00
let str: String = .init(localized: "about.version-\(AppInfo.appVersion)")
2024-01-27 08:56:22 +01:00
var body: some View {
ZStack {
Rectangle()
.fill(Color.red.gradient)
2024-01-27 08:56:22 +01:00
.ignoresSafeArea()
VStack {
ContentUnavailableView(String("Missing view for \"\(destId.isEmpty ? "[EMPTY_DEST_ID]" : destId)\""), systemImage: "exclamationmark.triangle.fill", description: Text(String("Please notify Lumaa as soon as possible!\n\n\(str)")))
.foregroundStyle(.white)
Button {
dismiss()
} label: {
Text(String("Dismiss"))
}
.buttonStyle(LargeButton(filled: true))
}
2024-01-27 08:56:22 +01:00
}
}
}