From 7f5330f284b43090fa24ba01d7e323a74346b51e Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Fri, 6 Jan 2023 17:14:34 +0100 Subject: [PATCH] Redesigned settings tab --- IceCubesApp.xcodeproj/project.pbxproj | 4 + IceCubesApp/App/AppRouteur.swift | 2 + .../Tabs/Settings/DisplaySettingsView.swift | 66 ++++++++++++++++ .../App/Tabs/Settings/InstanceInfoView.swift | 35 ++++---- .../App/Tabs/Settings/SettingsTab.swift | 79 ++++++++----------- .../Tabs/Timeline/AddRemoteTimelineVIew.swift | 9 +-- .../App/Tabs/Timeline/TimelineTab.swift | 41 +++------- .../Sources/DesignSystem/Theme.swift | 18 ++++- Packages/Env/Sources/Env/Routeur.swift | 3 + Packages/Models/Sources/Models/Status.swift | 2 +- .../Status/Row/StatusActionsView.swift | 7 +- .../Sources/Status/Row/StatusRowView.swift | 2 +- .../Sources/Timeline/TimelineFilter.swift | 6 +- 13 files changed, 171 insertions(+), 103 deletions(-) create mode 100644 IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift diff --git a/IceCubesApp.xcodeproj/project.pbxproj b/IceCubesApp.xcodeproj/project.pbxproj index 1dac7305..8ce87a58 100644 --- a/IceCubesApp.xcodeproj/project.pbxproj +++ b/IceCubesApp.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7335EE29674F7100AFF0BA /* QuickLook.framework */; }; 9F7335F22967608F00AFF0BA /* AddRemoteTimelineVIew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineVIew.swift */; }; 9F7335F72968274500AFF0BA /* AppAccountsSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F62968274500AFF0BA /* AppAccountsSelectorView.swift */; }; + 9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; }; 9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4ACA293783B000772766 /* SettingsTab.swift */; }; 9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAE4ACD29379A5A00772766 /* KeychainSwift */; }; 9FAE4AD129379AD600772766 /* AppAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4AD029379AD600772766 /* AppAccount.swift */; }; @@ -67,6 +68,7 @@ 9F7335EE29674F7100AFF0BA /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.2.sdk/System/Library/Frameworks/QuickLook.framework; sourceTree = DEVELOPER_DIR; }; 9F7335F12967608F00AFF0BA /* AddRemoteTimelineVIew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoteTimelineVIew.swift; sourceTree = ""; }; 9F7335F62968274500AFF0BA /* AppAccountsSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccountsSelectorView.swift; sourceTree = ""; }; + 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = ""; }; 9FAE4AC8293774FF00772766 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = ""; }; 9FAE4AD029379AD600772766 /* AppAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccount.swift; sourceTree = ""; }; @@ -212,6 +214,7 @@ 9FE151A5293C90F900E9683D /* IconSelectorView.swift */, 9F2B92F9295DA7D700DE16D0 /* AddAccountsView.swift */, 9F2B92FB295DA94500DE16D0 /* InstanceInfoView.swift */, + 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */, ); path = Settings; sourceTree = ""; @@ -306,6 +309,7 @@ 9F2B92FC295DA94500DE16D0 /* InstanceInfoView.swift in Sources */, 9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */, 9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */, + 9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */, 9FAE4AD32937A0C600772766 /* AppAccountsManager.swift in Sources */, 9F2B92FF295EB87100DE16D0 /* AppAccountView.swift in Sources */, 9F2B92F6295AE04800DE16D0 /* Tabs.swift in Sources */, diff --git a/IceCubesApp/App/AppRouteur.swift b/IceCubesApp/App/AppRouteur.swift index cd638d2b..70b02ee7 100644 --- a/IceCubesApp/App/AppRouteur.swift +++ b/IceCubesApp/App/AppRouteur.swift @@ -53,6 +53,8 @@ extension View { ListAddAccountView(account: account) case .addAccount: AddAccountView() + case .addRemoteLocalTimeline: + AddRemoteTimelineView() } } } diff --git a/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift new file mode 100644 index 00000000..e14cd1c4 --- /dev/null +++ b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift @@ -0,0 +1,66 @@ +import SwiftUI +import Models +import DesignSystem +import Status + +struct DisplaySettingsView: View { + @EnvironmentObject private var theme: Theme + + @State private var isThemeSelectorPresented = false + + var body: some View { + Form { + Section("Theme") { + themeSelectorButton + ColorPicker("Tint color", selection: $theme.tintColor) + ColorPicker("Background color", selection: $theme.primaryBackgroundColor) + ColorPicker("Secondary Background color", selection: $theme.secondaryBackgroundColor) + } + .listRowBackground(theme.primaryBackgroundColor) + + Section("Display") { + Picker("Avatar position", selection: $theme.avatarPosition) { + ForEach(Theme.AvatarPosition.allCases, id: \.rawValue) { position in + Text(position.description).tag(position) + } + } + Picker("Avatar shape", selection: $theme.avatarShape) { + ForEach(Theme.AvatarShape.allCases, id: \.rawValue) { shape in + Text(shape.description).tag(shape) + } + } + Picker("Status actions buttons", selection: $theme.statusActionsDisplay) { + ForEach(Theme.StatusActionsDisplay.allCases, id: \.rawValue) { buttonStyle in + Text(buttonStyle.description).tag(buttonStyle) + } + } + } + .listRowBackground(theme.primaryBackgroundColor) + + Section { + Button { + theme.selectedSet = .iceCubeDark + theme.avatarShape = .rounded + theme.avatarPosition = .top + theme.statusActionsDisplay = .full + } label: { + Text("Restore default") + } + } + .listRowBackground(theme.primaryBackgroundColor) + } + .navigationTitle("Display Settings") + .scrollContentBackground(.hidden) + .background(theme.secondaryBackgroundColor) + } + + private var themeSelectorButton: some View { + NavigationLink(destination: ThemePreviewView()) { + HStack { + Text("Theme") + Spacer() + Text(theme.selectedSet.rawValue) + } + } + } +} diff --git a/IceCubesApp/App/Tabs/Settings/InstanceInfoView.swift b/IceCubesApp/App/Tabs/Settings/InstanceInfoView.swift index 58f1f43a..8e3457f4 100644 --- a/IceCubesApp/App/Tabs/Settings/InstanceInfoView.swift +++ b/IceCubesApp/App/Tabs/Settings/InstanceInfoView.swift @@ -9,22 +9,27 @@ struct InstanceInfoView: View { let instance: Instance var body: some View { - Section("Instance info") { - LabeledContent("Name", value: instance.title) - Text(instance.shortDescription) - LabeledContent("Email", value: instance.email) - LabeledContent("Version", value: instance.version) - LabeledContent("Users", value: "\(instance.stats.userCount)") - LabeledContent("Posts", value: "\(instance.stats.statusCount)") - LabeledContent("Domains", value: "\(instance.stats.domainCount)") - } - .listRowBackground(theme.primaryBackgroundColor) - - Section("Instance rules") { - ForEach(instance.rules) { rule in - Text(rule.text) + Form { + Section("Instance info") { + LabeledContent("Name", value: instance.title) + Text(instance.shortDescription) + LabeledContent("Email", value: instance.email) + LabeledContent("Version", value: instance.version) + LabeledContent("Users", value: "\(instance.stats.userCount)") + LabeledContent("Posts", value: "\(instance.stats.statusCount)") + LabeledContent("Domains", value: "\(instance.stats.domainCount)") } + .listRowBackground(theme.primaryBackgroundColor) + + Section("Instance rules") { + ForEach(instance.rules) { rule in + Text(rule.text) + } + } + .listRowBackground(theme.primaryBackgroundColor) } - .listRowBackground(theme.primaryBackgroundColor) + .navigationTitle("Instance Info") + .scrollContentBackground(.hidden) + .background(theme.secondaryBackgroundColor) } } diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index 4c68e783..9ba4e46f 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -7,6 +7,7 @@ import Models import DesignSystem struct SettingsTabs: View { + @EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var client: Client @EnvironmentObject private var currentInstance: CurrentInstance @EnvironmentObject private var appAccountsManager: AppAccountsManager @@ -15,21 +16,21 @@ struct SettingsTabs: View { @StateObject private var routeurPath = RouterPath() @State private var addAccountSheetPresented = false - @State private var isThemeSelectorPresented = false var body: some View { NavigationStack(path: $routeurPath.path) { Form { appSection accountsSection - themeSection - instanceSection + generalSection } .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) .navigationTitle(Text("Settings")) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(theme.primaryBackgroundColor, for: .navigationBar) + .withAppRouteur() + .withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet) } .onAppear { routeurPath.client = client @@ -64,43 +65,29 @@ struct SettingsTabs: View { .listRowBackground(theme.primaryBackgroundColor) } - private var themeSection: some View { - Section("Theme") { - themeSelectorButton - ColorPicker("Tint color", selection: $theme.tintColor) - ColorPicker("Background color", selection: $theme.primaryBackgroundColor) - ColorPicker("Secondary Background color", selection: $theme.secondaryBackgroundColor) - Picker("Avatar position", selection: $theme.avatarPosition) { - ForEach(Theme.AvatarPosition.allCases, id: \.rawValue) { position in - Text(position.description).tag(position) + @ViewBuilder + private var generalSection: some View { + Section("General") { + if let instanceData = currentInstance.instance { + NavigationLink(destination: InstanceInfoView(instance: instanceData)) { + Label("Instance Information", systemImage: "server.rack") } } - Picker("Avatar shape", selection: $theme.avatarShape) { - ForEach(Theme.AvatarShape.allCases, id: \.rawValue) { shape in - Text(shape.description).tag(shape) - } + NavigationLink(destination: DisplaySettingsView()) { + Label("Display Settings", systemImage: "paintpalette") } - Button { - theme.selectedSet = .iceCubeDark - } label: { - Text("Restore default") + NavigationLink(destination: remoteLocalTimelinesView) { + Label("Remote Local Timelines", systemImage: "dot.radiowaves.right") } } .listRowBackground(theme.primaryBackgroundColor) } - @ViewBuilder - private var instanceSection: some View { - if let instanceData = currentInstance.instance { - InstanceInfoView(instance: instanceData) - } - } - private var appSection: some View { Section("App") { NavigationLink(destination: IconSelectorView()) { Label { - Text("Icon selector") + Text("App Icon") } icon: { if let icon = IconSelectorView.Icon(string: UIApplication.shared.alternateIconName ?? "AppIcon") { Image(uiImage: .init(named: icon.iconName)!) @@ -111,7 +98,7 @@ struct SettingsTabs: View { } } Link(destination: URL(string: "https://github.com/Dimillian/IceCubesApp")!) { - Text("https://github.com/Dimillian/IceCubesApp") + Label("Source (Github link)", systemImage: "link") } } .listRowBackground(theme.primaryBackgroundColor) @@ -128,25 +115,25 @@ struct SettingsTabs: View { } } - private var themeSelectorButton: some View { - NavigationLink(destination: ThemePreviewView()) { - Button { - isThemeSelectorPresented.toggle() - } label: { - HStack { - Text("Theme") - Spacer() - Text(theme.selectedSet.rawValue) + private var remoteLocalTimelinesView: some View { + Form { + ForEach(preferences.remoteLocalTimelines, id: \.self) { server in + Text(server) + }.onDelete { indexes in + if let index = indexes.first { + _ = preferences.remoteLocalTimelines.remove(at: index) } } + .listRowBackground(theme.primaryBackgroundColor) + Button { + routeurPath.presentedSheet = .addRemoteLocalTimeline + } label: { + Label("Add a local timeline", systemImage: "badge.plus.radiowaves.right") + } + .listRowBackground(theme.primaryBackgroundColor) } - } - - private var signOutButton: some View { - Button { - appAccountsManager.delete(account: appAccountsManager.currentAccount) - } label: { - Text("Sign out").foregroundColor(.red) - } + .navigationTitle("Remote Local Timelines") + .scrollContentBackground(.hidden) + .background(theme.secondaryBackgroundColor) } } diff --git a/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineVIew.swift b/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineVIew.swift index 9835d218..f8ee59e2 100644 --- a/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineVIew.swift +++ b/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineVIew.swift @@ -6,9 +6,10 @@ import DesignSystem import NukeUI import Shimmer -struct AddRemoteTimelineVIew: View { +struct AddRemoteTimelineView: View { @Environment(\.dismiss) private var dismiss + @EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var theme: Theme @State private var instanceName: String = "" @@ -16,9 +17,7 @@ struct AddRemoteTimelineVIew: View { @State private var instances: [InstanceSocial] = [] @FocusState private var isInstanceURLFieldFocused: Bool - - @Binding var addedInstance: String - + var body: some View { NavigationStack { Form { @@ -36,7 +35,7 @@ struct AddRemoteTimelineVIew: View { } Button { guard instance != nil else { return } - addedInstance = instanceName + preferences.remoteLocalTimelines.append(instanceName) dismiss() } label: { Text("Add") diff --git a/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift b/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift index 54073821..b288eec9 100644 --- a/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift +++ b/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift @@ -17,10 +17,7 @@ struct TimelineTab: View { @State private var didAppear: Bool = false @State private var timeline: TimelineFilter = .home @State private var scrollToTopSignal: Int = 0 - - @State private var newlyAddedLocalTimeline: String = "" - @State private var isAddRemoteLocalTimelinePresented: Bool = false - + var body: some View { NavigationStack(path: $routeurPath.path) { TimelineView(timeline: $timeline, scrollToTopSignal: $scrollToTopSignal) @@ -31,9 +28,6 @@ struct TimelineTab: View { } .id(currentAccount.account?.id) } - .sheet(isPresented: $isAddRemoteLocalTimelinePresented) { - AddRemoteTimelineVIew(addedInstance: $newlyAddedLocalTimeline) - } .onAppear { routeurPath.client = client if !didAppear { @@ -62,13 +56,6 @@ struct TimelineTab: View { .onChange(of: currentAccount.account?.id) { _ in routeurPath.path = [] } - .onChange(of: isAddRemoteLocalTimelinePresented) { isPresented in - if !isPresented && !newlyAddedLocalTimeline.isEmpty { - preferences.remoteLocalTimelines.append(newlyAddedLocalTimeline) - timeline = .remoteLocal(server: newlyAddedLocalTimeline) - newlyAddedLocalTimeline = "" - } - } .environmentObject(routeurPath) } @@ -106,24 +93,20 @@ struct TimelineTab: View { } } - if !preferences.remoteLocalTimelines.isEmpty { - Menu("Local Timelines") { - ForEach(preferences.remoteLocalTimelines, id: \.self) { server in - Button { - timeline = .remoteLocal(server: server) - } label: { - Label(server, systemImage: "dot.radiowaves.right") - } + Menu("Local Timelines") { + ForEach(preferences.remoteLocalTimelines, id: \.self) { server in + Button { + timeline = .remoteLocal(server: server) + } label: { + Label(server, systemImage: "dot.radiowaves.right") } } + Button { + routeurPath.presentedSheet = .addRemoteLocalTimeline + } label: { + Label("Add a local timeline", systemImage: "badge.plus.radiowaves.right") + } } - - Button { - isAddRemoteLocalTimelinePresented = true - } label: { - Label("Add a local timeline", systemImage: "badge.plus.radiowaves.right") - } - } private var addAccountButton: some View { diff --git a/Packages/DesignSystem/Sources/DesignSystem/Theme.swift b/Packages/DesignSystem/Sources/DesignSystem/Theme.swift index e5613d69..eb12381a 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Theme.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Theme.swift @@ -4,7 +4,7 @@ import SwiftUI public class Theme: ObservableObject { enum ThemeKey: String { case colorScheme, tint, label, primaryBackground, secondaryBackground - case avatarPosition, avatarShape + case avatarPosition, avatarShape, statusActionsDisplay case selectedSet, selectedScheme } @@ -34,6 +34,21 @@ public class Theme: ObservableObject { } } + public enum StatusActionsDisplay: String, CaseIterable { + case full, discret, none + + public var description: LocalizedStringKey { + switch self { + case .full: + return "All" + case .discret: + return "Only buttons" + case .none: + return "No buttons" + } + } + } + @AppStorage("is_previously_set") private var isSet: Bool = false @AppStorage(ThemeKey.selectedScheme.rawValue) public var selectedScheme: ColorScheme = .dark @AppStorage(ThemeKey.tint.rawValue) public var tintColor: Color = .black @@ -43,6 +58,7 @@ public class Theme: ObservableObject { @AppStorage(ThemeKey.avatarPosition.rawValue) var rawAvatarPosition: String = AvatarPosition.top.rawValue @AppStorage(ThemeKey.avatarShape.rawValue) var rawAvatarShape: String = AvatarShape.rounded.rawValue @AppStorage(ThemeKey.selectedSet.rawValue) var storedSet: ColorSetName = .iceCubeDark + @AppStorage(ThemeKey.statusActionsDisplay.rawValue) public var statusActionsDisplay: StatusActionsDisplay = .full @Published public var avatarPosition: AvatarPosition = .top @Published public var avatarShape: AvatarShape = .rounded diff --git a/Packages/Env/Sources/Env/Routeur.swift b/Packages/Env/Sources/Env/Routeur.swift index 5673a4a4..96e35d87 100644 --- a/Packages/Env/Sources/Env/Routeur.swift +++ b/Packages/Env/Sources/Env/Routeur.swift @@ -25,6 +25,7 @@ public enum SheetDestinations: Identifiable { case listEdit(list: Models.List) case listAddAccount(account: Account) case addAccount + case addRemoteLocalTimeline public var id: String { switch self { @@ -36,6 +37,8 @@ public enum SheetDestinations: Identifiable { return "listAddAccount" case .addAccount: return "addAccount" + case .addRemoteLocalTimeline: + return "addRemoteLocalTimeline" } } } diff --git a/Packages/Models/Sources/Models/Status.swift b/Packages/Models/Sources/Models/Status.swift index d7a7d920..aa203a8d 100644 --- a/Packages/Models/Sources/Models/Status.swift +++ b/Packages/Models/Sources/Models/Status.swift @@ -73,7 +73,7 @@ public struct Status: AnyStatus, Codable, Identifiable { public static func placeholder() -> Status { .init(id: UUID().uuidString, - content: "Some post content\n Some more post content \n Some more", + content: "This is a #toot\nWith some @content\nAnd some more content for your #eyes @only", account: .placeholder(), createdAt: "2022-12-16T10:20:54.000Z", editedAt: nil, diff --git a/Packages/Status/Sources/Status/Row/StatusActionsView.swift b/Packages/Status/Sources/Status/Row/StatusActionsView.swift index 60700b69..887a8e5a 100644 --- a/Packages/Status/Sources/Status/Row/StatusActionsView.swift +++ b/Packages/Status/Sources/Status/Row/StatusActionsView.swift @@ -29,7 +29,10 @@ struct StatusActionsView: View { } } - func count(viewModel: StatusRowViewModel) -> Int? { + func count(viewModel: StatusRowViewModel, theme: Theme) -> Int? { + if theme.statusActionsDisplay == .discret { + return nil + } switch self { case .respond: return viewModel.repliesCount @@ -71,7 +74,7 @@ struct StatusActionsView: View { HStack(spacing: 2) { Image(systemName: action.iconName(viewModel: viewModel)) .foregroundColor(action.tintColor(viewModel: viewModel, theme: theme)) - if let count = action.count(viewModel: viewModel) { + if let count = action.count(viewModel: viewModel, theme: theme) { Text("\(count)") .font(.footnote) } diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index c2da4dad..3e2bc91d 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -42,7 +42,7 @@ public struct StatusRowView: View { replyView } statusView - if !viewModel.isCompact && !viewModel.isRemote { + if !viewModel.isCompact && !viewModel.isRemote, theme.statusActionsDisplay != .none { StatusActionsView(viewModel: viewModel) .padding(.vertical, 8) .tint(viewModel.isFocused ? theme.tintColor : .gray) diff --git a/Packages/Timeline/Sources/Timeline/TimelineFilter.swift b/Packages/Timeline/Sources/Timeline/TimelineFilter.swift index d0f70812..1d3a5696 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineFilter.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineFilter.swift @@ -3,7 +3,7 @@ import Models import Network public enum TimelineFilter: Hashable, Equatable { - case federated, local, home, trending + case home, local, federated, trending case hashtag(tag: String, accountId: String?) case list(list: List) case remoteLocal(server: String) @@ -14,9 +14,9 @@ public enum TimelineFilter: Hashable, Equatable { public static func availableTimeline(client: Client) -> [TimelineFilter] { if !client.isAuth { - return [.federated, .local, .trending] + return [.local, .federated, .trending] } - return [.federated, .local, .trending, .home] + return [.home, .local, .federated, .trending] } public func title() -> String {