Add favorites / bookmarks tab on macOS / iPadOS

This commit is contained in:
Thomas Ricouard 2023-12-28 09:37:02 +01:00
parent f79580f746
commit b0ba6c15da
5 changed files with 164 additions and 1 deletions

View file

@ -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 */,

View file

@ -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)
}
}
}

View file

@ -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:
""
}

View file

@ -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
}
}
}

View file

@ -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) {
}
}