Redesigned settings tab

This commit is contained in:
Thomas Ricouard 2023-01-06 17:14:34 +01:00
parent 03afa1f978
commit 7f5330f284
13 changed files with 171 additions and 103 deletions

View file

@ -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 = "<group>"; };
9F7335F62968274500AFF0BA /* AppAccountsSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccountsSelectorView.swift; sourceTree = "<group>"; };
9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = "<group>"; };
9FAE4AC8293774FF00772766 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = "<group>"; };
9FAE4AD029379AD600772766 /* AppAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccount.swift; sourceTree = "<group>"; };
@ -212,6 +214,7 @@
9FE151A5293C90F900E9683D /* IconSelectorView.swift */,
9F2B92F9295DA7D700DE16D0 /* AddAccountsView.swift */,
9F2B92FB295DA94500DE16D0 /* InstanceInfoView.swift */,
9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */,
);
path = Settings;
sourceTree = "<group>";
@ -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 */,

View file

@ -53,6 +53,8 @@ extension View {
ListAddAccountView(account: account)
case .addAccount:
AddAccountView()
case .addRemoteLocalTimeline:
AddRemoteTimelineView()
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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