Followed Tags + Lists tab. + sidebar customization

This commit is contained in:
Thomas Ricouard 2024-01-10 13:26:55 +01:00
parent 6246d7b0a5
commit 0da8228e61
10 changed files with 329 additions and 18 deletions

View file

@ -83,6 +83,7 @@
9FB143D22983104A00A27BB1 /* glass.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9F2A542D296B1CC0009B2D7C /* glass.wav */; };
9FB183222AE9268800BBB692 /* IceCubesApp+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB183212AE9268800BBB692 /* IceCubesApp+Menu.swift */; };
9FB183292AE9449100BBB692 /* IceCubesApp+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB183282AE9449100BBB692 /* IceCubesApp+Scene.swift */; };
9FBA1D352B4E9B7E00ADB568 /* SidebarEntriesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA1D342B4E9B7E00ADB568 /* SidebarEntriesSettingsView.swift */; };
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */; };
9FBFE64E292A72BD00C250E9 /* Network in Frameworks */ = {isa = PBXBuildFile; productRef = 9FBFE64D292A72BD00C250E9 /* Network */; };
9FC14EF22B494D180006CEE1 /* TagsGroupSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC14EF12B494D180006CEE1 /* TagsGroupSettingView.swift */; };
@ -227,6 +228,7 @@
9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = "<group>"; };
9FB183212AE9268800BBB692 /* IceCubesApp+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IceCubesApp+Menu.swift"; sourceTree = "<group>"; };
9FB183282AE9449100BBB692 /* IceCubesApp+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IceCubesApp+Scene.swift"; sourceTree = "<group>"; };
9FBA1D342B4E9B7E00ADB568 /* SidebarEntriesSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarEntriesSettingsView.swift; sourceTree = "<group>"; };
9FBFE639292A715500C250E9 /* Ice Cubes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ice Cubes.app"; sourceTree = BUILT_PRODUCTS_DIR; };
9FBFE63C292A715500C250E9 /* IceCubesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IceCubesApp.swift; sourceTree = "<group>"; };
9FC14EF12B494D180006CEE1 /* TagsGroupSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsGroupSettingView.swift; sourceTree = "<group>"; };
@ -531,6 +533,7 @@
9FC14EF12B494D180006CEE1 /* TagsGroupSettingView.swift */,
9FC14EF32B494D940006CEE1 /* RemoteTimelinesSettingView.swift */,
9FC14EF52B494DFF0006CEE1 /* RecenTagsSettingView.swift */,
9FBA1D342B4E9B7E00ADB568 /* SidebarEntriesSettingsView.swift */,
);
path = Settings;
sourceTree = "<group>";
@ -852,6 +855,7 @@
9F6028562B3F36AE00476078 /* AppView.swift in Sources */,
9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */,
9F1E8B47298EBCBB00609F80 /* HapticSettingsView.swift in Sources */,
9FBA1D352B4E9B7E00ADB568 /* SidebarEntriesSettingsView.swift in Sources */,
9F2A5411296A1429009B2D7C /* PushNotificationsView.swift in Sources */,
9F6028582B3F3B7600476078 /* ToolbarTab.swift in Sources */,
);

View file

@ -26,6 +26,7 @@ struct AppView: View {
@State var popToRootTab: Tab = .other
@State var iosTabs = iOSTabs.shared
@State var sidebarTabs = SidebarTabs.shared
var body: some View {
#if os(visionOS)
@ -40,10 +41,15 @@ struct AppView: View {
}
var availableTabs: [Tab] {
if UIDevice.current.userInterfaceIdiom == .phone || horizontalSizeClass == .compact {
return appAccountsManager.currentClient.isAuth ? iosTabs.tabs : Tab.loggedOutTab()
guard appAccountsManager.currentClient.isAuth else {
return Tab.loggedOutTab()
}
return appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab()
if UIDevice.current.userInterfaceIdiom == .phone || horizontalSizeClass == .compact {
return iosTabs.tabs
} else if UIDevice.current.userInterfaceIdiom == .vision {
return Tab.visionOSTab()
}
return sidebarTabs.tabs.map{ $0.tab }
}
var tabBarView: some View {

View file

@ -17,11 +17,13 @@ struct SideBarView<Content: View>: View {
@Environment(StreamWatcher.self) private var watcher
@Environment(UserPreferences.self) private var userPreferences
@Environment(RouterPath.self) private var routerPath
@Binding var selectedTab: Tab
@Binding var popToRootTab: Tab
var tabs: [Tab]
@ViewBuilder var content: () -> Content
@State private var sidebarTabs = SidebarTabs.shared
private func badgeFor(tab: Tab) -> Int {
if tab == .notifications, selectedTab != tab,
@ -107,7 +109,7 @@ struct SideBarView<Content: View>: View {
private var tabsView: some View {
ForEach(tabs) { tab in
if tab != .profile {
if tab != .profile && sidebarTabs.isEnabled(tab) {
Button {
// ensure keyboard is always dismissed when selecting a tab
hideKeyboard()

View file

@ -164,6 +164,10 @@ struct SettingsTabs: View {
NavigationLink(destination: TabbarEntriesSettingsView()) {
Label("settings.general.tabbarEntries", systemImage: "platter.filled.bottom.iphone")
}
} else if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
NavigationLink(destination: SidebarEntriesSettingsView()) {
Label("settings.general.sidebarEntries", systemImage: "sidebar.squares.leading")
}
}
NavigationLink(destination: TranslationSettingsView()) {
Label("settings.general.translate", systemImage: "captions.bubble")

View file

@ -0,0 +1,39 @@
import DesignSystem
import Env
import SwiftUI
struct SidebarEntriesSettingsView: View {
@Environment(Theme.self) private var theme
@Environment(UserPreferences.self) private var userPreferences
@State private var sidebarTabs = SidebarTabs.shared
var body: some View {
@Bindable var userPreferences = userPreferences
Form {
Section {
ForEach($sidebarTabs.tabs, id: \.tab) { $tab in
if tab.tab != .profile && tab.tab != .settings {
Toggle(isOn: $tab.enabled) {
tab.tab.label
}
}
}
.onMove(perform: move)
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
}
.environment(\.editMode, .constant(.active))
.navigationTitle("settings.general.sidebarEntries")
#if !os(visionOS)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
#endif
}
func move(from source: IndexSet, to destination: Int) {
sidebarTabs.tabs.move(fromOffsets: source, toOffset: destination)
}
}

View file

@ -6,13 +6,15 @@ import StatusKit
import SwiftUI
@MainActor
enum Tab: Int, Identifiable, Hashable, CaseIterable {
enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
case timeline, notifications, mentions, explore, messages, settings, other
case trending, federated, local
case profile
case bookmarks
case favorites
case post
case followedTags
case lists
nonisolated var id: Int {
rawValue
@ -21,16 +23,9 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable {
static func loggedOutTab() -> [Tab] {
[.timeline, .settings]
}
static func loggedInTabs() -> [Tab] {
if UIDevice.current.userInterfaceIdiom == .pad ||
UIDevice.current.userInterfaceIdiom == .mac {
[.timeline, .trending, .federated, .local, .notifications, .mentions, .explore, .messages, .bookmarks, .favorites, .profile, .settings]
} else if UIDevice.current.userInterfaceIdiom == .vision {
[.profile, .timeline, .notifications, .mentions, .explore, .messages, .settings]
} else {
[.timeline, .notifications, .explore, .messages, .profile]
}
static func visionOSTab() -> [Tab] {
[.profile, .timeline, .notifications, .mentions, .explore, .messages, .settings]
}
@ViewBuilder
@ -64,6 +59,14 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable {
NavigationTab {
AccountStatusesListView(mode: .favorites)
}
case .followedTags:
NavigationTab {
FollowedTagsListView()
}
case .lists:
NavigationTab {
ListsListView()
}
case .post:
VStack { }
case .other:
@ -100,6 +103,10 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable {
Label("accessibility.tabs.profile.picker.favorites", systemImage: iconName)
case .post:
Label("menu.new-post", systemImage: iconName)
case .followedTags:
Label("timeline.filter.tags", systemImage: iconName)
case .lists:
Label("timeline.filter.lists", systemImage: iconName)
case .other:
EmptyView()
@ -134,12 +141,61 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable {
"star"
case .post:
"square.and.pencil"
case .followedTags:
"tag"
case .lists:
"list.bullet"
case .other:
""
}
}
}
@Observable
class SidebarTabs {
struct SidedebarTab: Hashable, Codable {
let tab: Tab
var enabled: Bool
}
class Storage {
@AppStorage("sidebar_tabs") var tabs: [SidedebarTab] = [
.init(tab: .timeline, enabled: true),
.init(tab: .trending, enabled: true),
.init(tab: .federated, enabled: true),
.init(tab: .local, enabled: true),
.init(tab: .notifications, enabled: true),
.init(tab: .mentions, enabled: true),
.init(tab: .messages, enabled: true),
.init(tab: .explore, enabled: true),
.init(tab: .bookmarks, enabled: true),
.init(tab: .favorites, enabled: true),
.init(tab: .followedTags, enabled: true),
.init(tab: .lists, enabled: true),
.init(tab: .settings, enabled: true),
.init(tab: .profile, enabled: true),
]
}
private let storage = Storage()
public static let shared = SidebarTabs()
var tabs: [SidedebarTab] {
didSet {
storage.tabs = tabs
}
}
func isEnabled(_ tab: Tab) -> Bool {
tabs.first(where: { $0.tab.id == tab.id })?.enabled == true
}
private init() {
tabs = storage.tabs
}
}
@Observable
class iOSTabs {
enum TabEntries: String {

View file

@ -47558,6 +47558,125 @@
}
}
},
"settings.general.sidebarEntries" : {
"extractionState" : "manual",
"localizations" : {
"be" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"ca" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sidebar Customizations"
}
},
"en-GB" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sidebar Customizations"
}
},
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"eu" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"fr" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"it" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"ja" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"ko" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"nb" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"nl" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"pl" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"tr" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"uk" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sidebar Customizations"
}
}
}
},
"settings.general.swipeactions" : {
"localizations" : {
"be" : {
@ -75118,4 +75237,4 @@
}
},
"version" : "1.0"
}
}

View file

@ -0,0 +1,46 @@
import DesignSystem
import Models
import SwiftUI
import Env
public struct ListsListView: View {
@Environment(CurrentAccount.self) private var currentAccount
@Environment(Theme.self) private var theme
public init() {}
public var body: some View {
List {
ForEach(currentAccount.lists) { list in
NavigationLink(value: RouterDestination.list(list: list)) {
Text(list.title)
.font(.scaledHeadline)
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
}
.onDelete { index in
if let index = index.first {
Task {
await currentAccount.deleteList(currentAccount.lists[index])
}
}
}
}
.task {
await currentAccount.fetchLists()
}
.refreshable {
await currentAccount.fetchLists()
}
#if !os(visionOS)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
#endif
.listStyle(.plain)
.navigationTitle("timeline.filter.lists")
.navigationBarTitleDisplayMode(.inline)
}
}

View file

@ -0,0 +1,35 @@
import DesignSystem
import Models
import SwiftUI
import Env
public struct FollowedTagsListView: View {
@Environment(CurrentAccount.self) private var currentAccount
@Environment(Theme.self) private var theme
public init() {}
public var body: some View {
List(currentAccount.tags) { tag in
TagRowView(tag: tag)
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
.padding(.vertical, 4)
}
.task {
await currentAccount.fetchFollowedTags()
}
.refreshable {
await currentAccount.fetchFollowedTags()
}
#if !os(visionOS)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
#endif
.listStyle(.plain)
.navigationTitle("timeline.filter.tags")
.navigationBarTitleDisplayMode(.inline)
}
}

View file

@ -88,7 +88,7 @@ import Observation
}
}
public func deleteList(list: Models.List) async {
public func deleteList(_ list: Models.List) async {
guard let client else { return }
lists.removeAll(where: { $0.id == list.id })
let response = try? await client.delete(endpoint: Lists.list(id: list.id))