Add favorites / bookmarks tab on macOS / iPadOS
This commit is contained in:
parent
f79580f746
commit
b0ba6c15da
|
@ -13,6 +13,7 @@
|
|||
069709A8298C87B5006E4CB5 /* OpenDyslexic-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 069709A7298C87B5006E4CB5 /* OpenDyslexic-Regular.otf */; };
|
||||
069709AA298C9AD7006E4CB5 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 069709A9298C9AD7006E4CB5 /* AboutView.swift */; };
|
||||
639CDF9C296AC82F00C35E58 /* SafariRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 639CDF9B296AC82F00C35E58 /* SafariRouter.swift */; };
|
||||
9F15D6002B3D6A850008C220 /* NavigationTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F15D5FF2B3D6A850008C220 /* NavigationTab.swift */; };
|
||||
9F18801229AE477F00D85459 /* tabSelection.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9F18800A29AE477E00D85459 /* tabSelection.wav */; };
|
||||
9F18801329AE477F00D85459 /* share.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9F18800B29AE477E00D85459 /* share.wav */; };
|
||||
9F18801429AE477F00D85459 /* bookmark.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9F18800C29AE477E00D85459 /* bookmark.wav */; };
|
||||
|
@ -160,6 +161,7 @@
|
|||
74D6453829945F6200B47B92 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
8C27D979298471E900CDF593 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
9664F1A8299BA5F700CBE70E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
9F15D5FF2B3D6A850008C220 /* NavigationTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTab.swift; sourceTree = "<group>"; };
|
||||
9F18800A29AE477E00D85459 /* tabSelection.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tabSelection.wav; sourceTree = "<group>"; };
|
||||
9F18800B29AE477E00D85459 /* share.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = share.wav; sourceTree = "<group>"; };
|
||||
9F18800C29AE477E00D85459 /* bookmark.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bookmark.wav; sourceTree = "<group>"; };
|
||||
|
@ -424,6 +426,7 @@
|
|||
9F55C68C2955968700F94077 /* ExploreTab.swift */,
|
||||
9F2B92F5295AE04800DE16D0 /* Tabs.swift */,
|
||||
9F4A48182976B21900A1A038 /* ProfileTab.swift */,
|
||||
9F15D5FF2B3D6A850008C220 /* NavigationTab.swift */,
|
||||
);
|
||||
path = Tabs;
|
||||
sourceTree = "<group>";
|
||||
|
@ -830,6 +833,7 @@
|
|||
FA31A9AB2A66BF7C00D5F662 /* EditTagGroupView.swift in Sources */,
|
||||
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */,
|
||||
9F398AA62935FE8A00A889F2 /* AppRegistry.swift in Sources */,
|
||||
9F15D6002B3D6A850008C220 /* NavigationTab.swift in Sources */,
|
||||
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */,
|
||||
9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */,
|
||||
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import SwiftUI
|
||||
import Env
|
||||
|
||||
@MainActor
|
||||
struct NavigationTab<Content: View>: View {
|
||||
var content: () -> Content
|
||||
|
||||
@State private var routerPath = RouterPath()
|
||||
|
||||
init(@ViewBuilder content: @escaping () -> Content) {
|
||||
self.content = content
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $routerPath.path) {
|
||||
content()
|
||||
.withEnvironments()
|
||||
.withAppRouter()
|
||||
.environment(routerPath)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ enum Tab: Int, Identifiable, Hashable {
|
|||
case timeline, notifications, mentions, explore, messages, settings, other
|
||||
case trending, federated, local
|
||||
case profile
|
||||
case bookmarks
|
||||
case favorites
|
||||
|
||||
nonisolated var id: Int {
|
||||
rawValue
|
||||
|
@ -22,7 +24,7 @@ enum Tab: Int, Identifiable, Hashable {
|
|||
static func loggedInTabs() -> [Tab] {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad ||
|
||||
UIDevice.current.userInterfaceIdiom == .mac {
|
||||
[.timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .settings]
|
||||
[.timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .bookmarks, .favorites, .settings]
|
||||
} else if UIDevice.current.userInterfaceIdiom == .vision {
|
||||
[.profile, .timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .settings]
|
||||
} else {
|
||||
|
@ -53,6 +55,14 @@ enum Tab: Int, Identifiable, Hashable {
|
|||
SettingsTabs(popToRootTab: popToRootTab, isModal: false)
|
||||
case .profile:
|
||||
ProfileTab(popToRootTab: popToRootTab)
|
||||
case .bookmarks:
|
||||
NavigationTab {
|
||||
AccountStatusesListView(mode: .bookmarks)
|
||||
}
|
||||
case .favorites:
|
||||
NavigationTab {
|
||||
AccountStatusesListView(mode: .favorites)
|
||||
}
|
||||
case .other:
|
||||
EmptyView()
|
||||
}
|
||||
|
@ -81,6 +91,10 @@ enum Tab: Int, Identifiable, Hashable {
|
|||
Label("tab.settings", systemImage: iconName)
|
||||
case .profile:
|
||||
Label("tab.profile", systemImage: iconName)
|
||||
case .bookmarks:
|
||||
Label("accessibility.tabs.profile.picker.bookmarks", systemImage: iconName)
|
||||
case .favorites:
|
||||
Label("accessibility.tabs.profile.picker.favorites", systemImage: iconName)
|
||||
case .other:
|
||||
EmptyView()
|
||||
}
|
||||
|
@ -108,6 +122,10 @@ enum Tab: Int, Identifiable, Hashable {
|
|||
"gear"
|
||||
case .profile:
|
||||
"person.crop.circle"
|
||||
case .bookmarks:
|
||||
"bookmark"
|
||||
case .favorites:
|
||||
"star"
|
||||
case .other:
|
||||
""
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import Status
|
||||
import Network
|
||||
import SwiftUI
|
||||
import Env
|
||||
import Models
|
||||
import DesignSystem
|
||||
|
||||
@MainActor
|
||||
public struct AccountStatusesListView: View {
|
||||
@Environment(Theme.self) private var theme
|
||||
@Environment(Client.self) private var client
|
||||
@Environment(RouterPath.self) private var routerPath
|
||||
|
||||
@State private var viewModel: AccountStatusesListViewModel
|
||||
@State private var isLoaded = false
|
||||
|
||||
public init(mode: AccountStatusesListViewModel.Mode) {
|
||||
_viewModel = .init(initialValue: .init(mode: mode))
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
List {
|
||||
StatusesListView(fetcher: viewModel, client: client, routerPath: routerPath)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
.navigationTitle(viewModel.mode.title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.refreshable {
|
||||
await viewModel.fetchNewestStatuses()
|
||||
}
|
||||
.task {
|
||||
guard !isLoaded else { return }
|
||||
viewModel.client = client
|
||||
await viewModel.fetchNewestStatuses()
|
||||
isLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
import SwiftUI
|
||||
import Models
|
||||
import Status
|
||||
import Network
|
||||
import Env
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
public class AccountStatusesListViewModel: StatusesFetcher {
|
||||
public enum Mode {
|
||||
case bookmarks, favorites
|
||||
|
||||
var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .bookmarks:
|
||||
"accessibility.tabs.profile.picker.bookmarks"
|
||||
case .favorites:
|
||||
"accessibility.tabs.profile.picker.favorites"
|
||||
}
|
||||
}
|
||||
|
||||
func endpoint(sinceId: String?) -> Endpoint {
|
||||
switch self {
|
||||
case .bookmarks:
|
||||
Accounts.bookmarks(sinceId: sinceId)
|
||||
case .favorites:
|
||||
Accounts.favorites(sinceId: sinceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mode: Mode
|
||||
public var statusesState: StatusesState = .loading
|
||||
var statuses: [Status] = []
|
||||
var nextPage: LinkHandler?
|
||||
|
||||
var client: Client?
|
||||
|
||||
init(mode: Mode) {
|
||||
self.mode = mode
|
||||
}
|
||||
|
||||
public func fetchNewestStatuses() async {
|
||||
guard let client else { return }
|
||||
statusesState = .loading
|
||||
do {
|
||||
(statuses, nextPage) = try await client.getWithLink(endpoint: mode.endpoint(sinceId: nil))
|
||||
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
||||
statusesState = .display(statuses: statuses,
|
||||
nextPageState: nextPage?.maxId != nil ? .hasNextPage : .none)
|
||||
} catch {
|
||||
statusesState = .error(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchNextPage() async {
|
||||
guard let client, let nextId = nextPage?.maxId else { return }
|
||||
statusesState = .display(statuses: statuses,
|
||||
nextPageState: .loadingNextPage)
|
||||
do {
|
||||
var newStatuses: [Status] = []
|
||||
(newStatuses, nextPage) = try await client.getWithLink(endpoint: mode.endpoint(sinceId: nextId))
|
||||
statuses.append(contentsOf: newStatuses)
|
||||
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
||||
statusesState = .display(statuses: statuses,
|
||||
nextPageState: nextPage?.maxId != nil ? .hasNextPage : .none)
|
||||
} catch { }
|
||||
}
|
||||
|
||||
public func statusDidAppear(status: Status) {
|
||||
|
||||
}
|
||||
|
||||
public func statusDidDisappear(status: Status) {
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue