Add more lists setttings

This commit is contained in:
Thomas Ricouard 2023-11-28 09:18:52 +01:00
parent d2f7ab1464
commit 2e2a9f5f14
12 changed files with 1665 additions and 98 deletions

View file

@ -82,6 +82,9 @@ extension View {
case let .mentionStatusEditor(account, visibility): case let .mentionStatusEditor(account, visibility):
StatusEditorView(mode: .mention(account: account, visibility: visibility)) StatusEditorView(mode: .mention(account: account, visibility: visibility))
.withEnvironments() .withEnvironments()
case .listCreate:
ListCreateView()
.withEnvironments()
case let .listEdit(list): case let .listEdit(list):
ListEditView(list: list) ListEditView(list: list)
.withEnvironments() .withEnvironments()

View file

@ -143,6 +143,11 @@ struct TimelineTab: View {
Label(list.title, systemImage: "list.bullet") Label(list.title, systemImage: "list.bullet")
} }
} }
Button {
routerPath.presentedSheet = .listCreate
} label: {
Label("account.list.create", systemImage: "plus")
}
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -22,8 +22,6 @@ public struct AccountDetailView: View {
@State private var viewModel: AccountDetailViewModel @State private var viewModel: AccountDetailViewModel
@State private var isCurrentUser: Bool = false @State private var isCurrentUser: Bool = false
@State private var isCreateListAlertPresented: Bool = false
@State private var createListTitle: String = ""
@State private var showBlockConfirmation: Bool = false @State private var showBlockConfirmation: Bool = false
@State private var isEditingAccount: Bool = false @State private var isEditingAccount: Bool = false
@ -262,7 +260,7 @@ public struct AccountDetailView: View {
} }
} }
Button("account.list.create") { Button("account.list.create") {
isCreateListAlertPresented = true routerPath.presentedSheet = .listCreate
} }
.tint(theme.tintColor) .tint(theme.tintColor)
.buttonStyle(.borderless) .buttonStyle(.borderless)
@ -271,23 +269,6 @@ public struct AccountDetailView: View {
.task { .task {
await currentAccount.fetchLists() await currentAccount.fetchLists()
} }
.alert("account.list.create", isPresented: $isCreateListAlertPresented) {
TextField("account.list.name", text: $createListTitle)
Button("action.cancel") {
isCreateListAlertPresented = false
createListTitle = ""
}
Button("account.list.create.confirm") {
guard !createListTitle.isEmpty else { return }
isCreateListAlertPresented = false
Task {
await currentAccount.createList(title: createListTitle)
createListTitle = ""
}
}
} message: {
Text("account.list.create.description")
}
} }
@ViewBuilder @ViewBuilder

View file

@ -87,15 +87,7 @@ import Observation
tags = [] tags = []
} }
} }
public func createList(title: String) async {
guard let client else { return }
do {
let list: Models.List = try await client.post(endpoint: Lists.createList(title: title))
lists.append(list)
} catch {}
}
public func deleteList(list: Models.List) async { public func deleteList(list: Models.List) async {
guard let client else { return } guard let client else { return }
lists.removeAll(where: { $0.id == list.id }) lists.removeAll(where: { $0.id == list.id })

View file

@ -42,6 +42,7 @@ public enum SheetDestination: Identifiable {
case replyToStatusEditor(status: Status) case replyToStatusEditor(status: Status)
case quoteStatusEditor(status: Status) case quoteStatusEditor(status: Status)
case mentionStatusEditor(account: Account, visibility: Models.Visibility) case mentionStatusEditor(account: Account, visibility: Models.Visibility)
case listCreate
case listEdit(list: Models.List) case listEdit(list: Models.List)
case listAddAccount(account: Account) case listAddAccount(account: Account)
case addAccount case addAccount
@ -59,6 +60,8 @@ public enum SheetDestination: Identifiable {
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor, case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor,
.mentionStatusEditor, .settings, .accountPushNotficationsSettings: .mentionStatusEditor, .settings, .accountPushNotficationsSettings:
"statusEditor" "statusEditor"
case .listCreate:
"listCreate"
case .listEdit: case .listEdit:
"listEdit" "listEdit"
case .listAddAccount: case .listAddAccount:

View file

@ -12,9 +12,6 @@ public struct ListAddAccountView: View {
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@State private var viewModel: ListAddAccountViewModel @State private var viewModel: ListAddAccountViewModel
@State private var isCreateListAlertPresented: Bool = false
@State private var createListTitle: String = ""
public init(account: Account) { public init(account: Account) {
_viewModel = .init(initialValue: .init(account: account)) _viewModel = .init(initialValue: .init(account: account))
} }
@ -40,10 +37,6 @@ public struct ListAddAccountView: View {
} }
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
} }
Button("lists.create") {
isCreateListAlertPresented = true
}
.listRowBackground(theme.primaryBackgroundColor)
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
@ -56,23 +49,6 @@ public struct ListAddAccountView: View {
} }
} }
} }
.alert("lists.create", isPresented: $isCreateListAlertPresented) {
TextField("lists.name", text: $createListTitle)
Button("action.cancel") {
isCreateListAlertPresented = false
createListTitle = ""
}
Button("lists.create.confirm") {
guard !createListTitle.isEmpty else { return }
isCreateListAlertPresented = false
Task {
await currentAccount.createList(title: createListTitle)
createListTitle = ""
}
}
} message: {
Text("lists.name.message")
}
} }
.task { .task {
viewModel.client = client viewModel.client = client

View file

@ -0,0 +1,65 @@
import DesignSystem
import EmojiText
import Models
import Network
import SwiftUI
import Env
@MainActor
public struct ListCreateView: View {
@Environment(\.dismiss) private var dismiss
@Environment(Theme.self) private var theme
@Environment(Client.self) private var client
@Environment(CurrentAccount.self) private var currentAccount
@State private var title = ""
@State private var repliesPolicy: Models.List.RepliesPolicy = .list
@State private var isExclusive: Bool = false
@State private var isSaving: Bool = false
public init() { }
public var body: some View {
NavigationStack {
Form {
Section("lists.edit.settings") {
TextField("list.edit.title", text: $title)
Picker("list.edit.repliesPolicy",
selection: $repliesPolicy) {
ForEach(Models.List.RepliesPolicy.allCases) { policy in
Text(policy.title)
.tag(policy)
}
}
Toggle("list.edit.isExclusive", isOn: $isExclusive)
}
.listRowBackground(theme.primaryBackgroundColor)
}
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.toolbar {
ToolbarItem {
Button {
Task {
isSaving = true
let _: Models.List = try await client.post(endpoint: Lists.createList(title: title,
repliesPolicy: repliesPolicy,
exclusive: isExclusive ))
await currentAccount.fetchLists()
isSaving = false
dismiss()
}
} label: {
if isSaving {
ProgressView()
} else {
Text("lists.create.confirm")
}
}
}
}
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
}
}
}

View file

@ -18,7 +18,29 @@ public struct ListEditView: View {
public var body: some View { public var body: some View {
NavigationStack { NavigationStack {
List { Form {
Section("lists.edit.settings") {
TextField("list.edit.title", text: $viewModel.title) {
Task { await viewModel.update() }
}
Picker("list.edit.repliesPolicy",
selection: $viewModel.repliesPolicy) {
ForEach(Models.List.RepliesPolicy.allCases) { policy in
Text(policy.title)
.tag(policy)
}
}
Toggle("list.edit.isExclusive", isOn: $viewModel.isExclusive)
}
.listRowBackground(theme.primaryBackgroundColor)
.disabled(viewModel.isUpdating)
.onChange(of: viewModel.repliesPolicy) { _, _ in
Task { await viewModel.update() }
}
.onChange(of: viewModel.isExclusive) { _, _ in
Task { await viewModel.update() }
}
Section("lists.edit.users-in-list") { Section("lists.edit.users-in-list") {
if viewModel.isLoadingAccounts { if viewModel.isLoadingAccounts {
HStack { HStack {
@ -26,7 +48,6 @@ public struct ListEditView: View {
ProgressView() ProgressView()
Spacer() Spacer()
} }
.listRowBackground(theme.primaryBackgroundColor)
} else { } else {
ForEach(viewModel.accounts) { account in ForEach(viewModel.accounts) { account in
HStack { HStack {
@ -41,7 +62,6 @@ public struct ListEditView: View {
.font(.scaledFootnote) .font(.scaledFootnote)
} }
} }
.listRowBackground(theme.primaryBackgroundColor)
}.onDelete { indexes in }.onDelete { indexes in
if let index = indexes.first { if let index = indexes.first {
Task { Task {
@ -52,6 +72,7 @@ public struct ListEditView: View {
} }
} }
} }
.listRowBackground(theme.primaryBackgroundColor)
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)

View file

@ -3,18 +3,28 @@ import Models
import Network import Network
import Observation import Observation
import SwiftUI import SwiftUI
import Env
@MainActor @MainActor
@Observable public class ListEditViewModel { @Observable public class ListEditViewModel {
let list: Models.List var list: Models.List
var client: Client? var client: Client?
var isLoadingAccounts: Bool = true var isLoadingAccounts: Bool = true
var accounts: [Account] = [] var accounts: [Account] = []
var title: String
var repliesPolicy: Models.List.RepliesPolicy
var isExclusive: Bool
var isUpdating: Bool = false
init(list: Models.List) { init(list: Models.List) {
self.list = list self.list = list
self.title = list.title
self.repliesPolicy = list.repliesPolicy
self.isExclusive = list.exclusive
} }
func fetchAccounts() async { func fetchAccounts() async {
@ -27,7 +37,27 @@ import SwiftUI
isLoadingAccounts = false isLoadingAccounts = false
} }
} }
func update() async {
guard let client else { return }
do {
isUpdating = true
let list: Models.List = try await client.put(endpoint:
Lists.updateList(id: list.id,
title: title,
repliesPolicy: repliesPolicy,
exclusive: isExclusive ))
self.list = list
self.title = list.title
self.repliesPolicy = list.repliesPolicy
self.isExclusive = list.exclusive
self.isUpdating = false
await CurrentAccount.shared.fetchLists()
} catch {
isUpdating = false
}
}
func delete(account: Account) async { func delete(account: Account) async {
guard let client else { return } guard let client else { return }
do { do {
@ -38,3 +68,16 @@ import SwiftUI
} catch {} } catch {}
} }
} }
extension Models.List.RepliesPolicy {
var title: LocalizedStringKey {
switch self {
case .followed:
return "list.repliesPolicy.followed"
case .list:
return "list.repliesPolicy.list"
case .none:
return "list.repliesPolicy.none"
}
}
}

View file

@ -3,7 +3,16 @@ import Foundation
public struct List: Codable, Identifiable, Equatable, Hashable { public struct List: Codable, Identifiable, Equatable, Hashable {
public let id: String public let id: String
public let title: String public let title: String
public let repliesPolicy: String public let repliesPolicy: RepliesPolicy
public let exclusive: Bool
public enum RepliesPolicy: String, Sendable, Codable, CaseIterable, Identifiable {
public var id: String {
rawValue
}
case followed, list, `none`
}
} }
extension List: Sendable {} extension List: Sendable {}

View file

@ -1,9 +1,11 @@
import Foundation import Foundation
import Models
public enum Lists: Endpoint { public enum Lists: Endpoint {
case lists case lists
case list(id: String) case list(id: String)
case createList(title: String) case createList(title: String, repliesPolicy: List.RepliesPolicy, exclusive: Bool)
case updateList(id: String, title: String, repliesPolicy: List.RepliesPolicy, exclusive: Bool)
case accounts(listId: String) case accounts(listId: String)
case updateAccounts(listId: String, accounts: [String]) case updateAccounts(listId: String, accounts: [String])
@ -11,7 +13,7 @@ public enum Lists: Endpoint {
switch self { switch self {
case .lists, .createList: case .lists, .createList:
"lists" "lists"
case let .list(id): case let .list(id), let .updateList(id, _, _, _):
"lists/\(id)" "lists/\(id)"
case let .accounts(listId): case let .accounts(listId):
"lists/\(listId)/accounts" "lists/\(listId)/accounts"
@ -24,8 +26,11 @@ public enum Lists: Endpoint {
switch self { switch self {
case .accounts: case .accounts:
return [.init(name: "limit", value: String(0))] return [.init(name: "limit", value: String(0))]
case let .createList(title): case let .createList(title, repliesPolicy, exclusive),
return [.init(name: "title", value: title)] let .updateList(_, title, repliesPolicy, exclusive):
return [.init(name: "title", value: title),
.init(name: "replies_policy", value: repliesPolicy.rawValue),
.init(name: "exclusive", value: exclusive ? "true" : "false")]
case let .updateAccounts(_, accounts): case let .updateAccounts(_, accounts):
var params: [URLQueryItem] = [] var params: [URLQueryItem] = []
for account in accounts { for account in accounts {