mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-04-27 02:14:45 +00:00
Add favorites / bookmarks tab on macOS / iPadOS
This commit is contained in:
parent
f79580f746
commit
b0ba6c15da
5 changed files with 164 additions and 1 deletions
|
@ -13,6 +13,7 @@
|
||||||
069709A8298C87B5006E4CB5 /* OpenDyslexic-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 069709A7298C87B5006E4CB5 /* OpenDyslexic-Regular.otf */; };
|
069709A8298C87B5006E4CB5 /* OpenDyslexic-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 069709A7298C87B5006E4CB5 /* OpenDyslexic-Regular.otf */; };
|
||||||
069709AA298C9AD7006E4CB5 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 069709A9298C9AD7006E4CB5 /* AboutView.swift */; };
|
069709AA298C9AD7006E4CB5 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 069709A9298C9AD7006E4CB5 /* AboutView.swift */; };
|
||||||
639CDF9C296AC82F00C35E58 /* SafariRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 639CDF9B296AC82F00C35E58 /* SafariRouter.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 */; };
|
9F18801229AE477F00D85459 /* tabSelection.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9F18800A29AE477E00D85459 /* tabSelection.wav */; };
|
||||||
9F18801329AE477F00D85459 /* share.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9F18800B29AE477E00D85459 /* share.wav */; };
|
9F18801329AE477F00D85459 /* share.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9F18800B29AE477E00D85459 /* share.wav */; };
|
||||||
9F18801429AE477F00D85459 /* bookmark.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9F18800C29AE477E00D85459 /* bookmark.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
9F18800C29AE477E00D85459 /* bookmark.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bookmark.wav; sourceTree = "<group>"; };
|
||||||
|
@ -424,6 +426,7 @@
|
||||||
9F55C68C2955968700F94077 /* ExploreTab.swift */,
|
9F55C68C2955968700F94077 /* ExploreTab.swift */,
|
||||||
9F2B92F5295AE04800DE16D0 /* Tabs.swift */,
|
9F2B92F5295AE04800DE16D0 /* Tabs.swift */,
|
||||||
9F4A48182976B21900A1A038 /* ProfileTab.swift */,
|
9F4A48182976B21900A1A038 /* ProfileTab.swift */,
|
||||||
|
9F15D5FF2B3D6A850008C220 /* NavigationTab.swift */,
|
||||||
);
|
);
|
||||||
path = Tabs;
|
path = Tabs;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -830,6 +833,7 @@
|
||||||
FA31A9AB2A66BF7C00D5F662 /* EditTagGroupView.swift in Sources */,
|
FA31A9AB2A66BF7C00D5F662 /* EditTagGroupView.swift in Sources */,
|
||||||
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */,
|
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */,
|
||||||
9F398AA62935FE8A00A889F2 /* AppRegistry.swift in Sources */,
|
9F398AA62935FE8A00A889F2 /* AppRegistry.swift in Sources */,
|
||||||
|
9F15D6002B3D6A850008C220 /* NavigationTab.swift in Sources */,
|
||||||
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */,
|
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */,
|
||||||
9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */,
|
9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */,
|
||||||
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */,
|
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */,
|
||||||
|
|
22
IceCubesApp/App/Tabs/NavigationTab.swift
Normal file
22
IceCubesApp/App/Tabs/NavigationTab.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ enum Tab: Int, Identifiable, Hashable {
|
||||||
case timeline, notifications, mentions, explore, messages, settings, other
|
case timeline, notifications, mentions, explore, messages, settings, other
|
||||||
case trending, federated, local
|
case trending, federated, local
|
||||||
case profile
|
case profile
|
||||||
|
case bookmarks
|
||||||
|
case favorites
|
||||||
|
|
||||||
nonisolated var id: Int {
|
nonisolated var id: Int {
|
||||||
rawValue
|
rawValue
|
||||||
|
@ -22,7 +24,7 @@ enum Tab: Int, Identifiable, Hashable {
|
||||||
static func loggedInTabs() -> [Tab] {
|
static func loggedInTabs() -> [Tab] {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad ||
|
if UIDevice.current.userInterfaceIdiom == .pad ||
|
||||||
UIDevice.current.userInterfaceIdiom == .mac {
|
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 {
|
} else if UIDevice.current.userInterfaceIdiom == .vision {
|
||||||
[.profile, .timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .settings]
|
[.profile, .timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .settings]
|
||||||
} else {
|
} else {
|
||||||
|
@ -53,6 +55,14 @@ enum Tab: Int, Identifiable, Hashable {
|
||||||
SettingsTabs(popToRootTab: popToRootTab, isModal: false)
|
SettingsTabs(popToRootTab: popToRootTab, isModal: false)
|
||||||
case .profile:
|
case .profile:
|
||||||
ProfileTab(popToRootTab: popToRootTab)
|
ProfileTab(popToRootTab: popToRootTab)
|
||||||
|
case .bookmarks:
|
||||||
|
NavigationTab {
|
||||||
|
AccountStatusesListView(mode: .bookmarks)
|
||||||
|
}
|
||||||
|
case .favorites:
|
||||||
|
NavigationTab {
|
||||||
|
AccountStatusesListView(mode: .favorites)
|
||||||
|
}
|
||||||
case .other:
|
case .other:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
@ -81,6 +91,10 @@ enum Tab: Int, Identifiable, Hashable {
|
||||||
Label("tab.settings", systemImage: iconName)
|
Label("tab.settings", systemImage: iconName)
|
||||||
case .profile:
|
case .profile:
|
||||||
Label("tab.profile", systemImage: iconName)
|
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:
|
case .other:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
@ -108,6 +122,10 @@ enum Tab: Int, Identifiable, Hashable {
|
||||||
"gear"
|
"gear"
|
||||||
case .profile:
|
case .profile:
|
||||||
"person.crop.circle"
|
"person.crop.circle"
|
||||||
|
case .bookmarks:
|
||||||
|
"bookmark"
|
||||||
|
case .favorites:
|
||||||
|
"star"
|
||||||
case .other:
|
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 a new issue