New account settings + create / edit server side filters
This commit is contained in:
parent
d0f16c84f7
commit
5cd9ddd945
@ -47,6 +47,7 @@
|
|||||||
9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */; };
|
9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */; };
|
||||||
9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; };
|
9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; };
|
||||||
9F7D93942980063100EE6B7A /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7D93932980063100EE6B7A /* AppAccount */; };
|
9F7D93942980063100EE6B7A /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7D93932980063100EE6B7A /* AppAccount */; };
|
||||||
|
9F7D939A29805DBD00EE6B7A /* AccountSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */; };
|
||||||
9F8CA5972979B61100481E8E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9B576C529743F4C00BCE646 /* Localizable.strings */; };
|
9F8CA5972979B61100481E8E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9B576C529743F4C00BCE646 /* Localizable.strings */; };
|
||||||
9F8CA5982979B63D00481E8E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9B576C529743F4C00BCE646 /* Localizable.strings */; };
|
9F8CA5982979B63D00481E8E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9B576C529743F4C00BCE646 /* Localizable.strings */; };
|
||||||
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; };
|
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; };
|
||||||
@ -156,6 +157,7 @@
|
|||||||
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "IceCubesApp-release.xcconfig"; sourceTree = "<group>"; };
|
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "IceCubesApp-release.xcconfig"; sourceTree = "<group>"; };
|
||||||
9F7D939B2980F5C100EE6B7A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
9F7D939B2980F5C100EE6B7A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
9F7D939C2980F5C200EE6B7A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
9F7D939C2980F5C200EE6B7A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingView.swift; sourceTree = "<group>"; };
|
||||||
9FAD85822971BF7200496AB1 /* Secret.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Secret.plist; sourceTree = "<group>"; };
|
9FAD85822971BF7200496AB1 /* Secret.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Secret.plist; sourceTree = "<group>"; };
|
||||||
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
9FAD858A29743F7400496AB1 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
9FAD858A29743F7400496AB1 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||||
@ -379,6 +381,7 @@
|
|||||||
9F2A540629699698009B2D7C /* SupportAppView.swift */,
|
9F2A540629699698009B2D7C /* SupportAppView.swift */,
|
||||||
9F2A5410296A1429009B2D7C /* PushNotificationsView.swift */,
|
9F2A5410296A1429009B2D7C /* PushNotificationsView.swift */,
|
||||||
C9B22676297F6C2E001F9EFE /* ContentSettingsView.swift */,
|
C9B22676297F6C2E001F9EFE /* ContentSettingsView.swift */,
|
||||||
|
9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */,
|
||||||
);
|
);
|
||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -591,6 +594,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */,
|
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */,
|
||||||
|
9F7D939A29805DBD00EE6B7A /* AccountSettingView.swift in Sources */,
|
||||||
9F2B92FC295DA94500DE16D0 /* InstanceInfoView.swift in Sources */,
|
9F2B92FC295DA94500DE16D0 /* InstanceInfoView.swift in Sources */,
|
||||||
C9B22677297F6C2E001F9EFE /* ContentSettingsView.swift in Sources */,
|
C9B22677297F6C2E001F9EFE /* ContentSettingsView.swift in Sources */,
|
||||||
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */,
|
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */,
|
||||||
|
@ -17,6 +17,8 @@ extension View {
|
|||||||
AccountDetailView(accountId: id)
|
AccountDetailView(accountId: id)
|
||||||
case let .accountDetailWithAccount(account):
|
case let .accountDetailWithAccount(account):
|
||||||
AccountDetailView(account: account)
|
AccountDetailView(account: account)
|
||||||
|
case let .accountSettingsWithAccount(account, appAccount):
|
||||||
|
AccountSettingsView(account: account, appAccount: appAccount)
|
||||||
case let .statusDetail(id):
|
case let .statusDetail(id):
|
||||||
StatusDetailView(statusId: id)
|
StatusDetailView(statusId: id)
|
||||||
case let .conversationDetail(conversation):
|
case let .conversationDetail(conversation):
|
||||||
|
@ -3,6 +3,7 @@ import AppAccount
|
|||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Models
|
||||||
|
|
||||||
struct SideBarView<Content: View>: View {
|
struct SideBarView<Content: View>: View {
|
||||||
@EnvironmentObject private var appAccounts: AppAccountsManager
|
@EnvironmentObject private var appAccounts: AppAccountsManager
|
||||||
|
81
IceCubesApp/App/Tabs/Settings/AccountSettingView.swift
Normal file
81
IceCubesApp/App/Tabs/Settings/AccountSettingView.swift
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import Account
|
||||||
|
import DesignSystem
|
||||||
|
import Env
|
||||||
|
import Models
|
||||||
|
import AppAccount
|
||||||
|
|
||||||
|
struct AccountSettingsView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
@EnvironmentObject private var pushNotifications: PushNotificationsService
|
||||||
|
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||||
|
@EnvironmentObject private var currentInstance: CurrentInstance
|
||||||
|
@EnvironmentObject private var theme: Theme
|
||||||
|
@EnvironmentObject private var appAccountsManager: AppAccountsManager
|
||||||
|
|
||||||
|
@State private var isEditingAccount: Bool = false
|
||||||
|
@State private var isEditingFilters: Bool = false
|
||||||
|
|
||||||
|
let account: Account
|
||||||
|
let appAccount: AppAccount
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
NavigationLink(value: RouterDestinations.accountDetailWithAccount(account: account)) {
|
||||||
|
Label("See Profile", systemImage: "person.crop.circle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
Section {
|
||||||
|
Label("Edit profile", systemImage: "pencil")
|
||||||
|
.onTapGesture {
|
||||||
|
isEditingAccount = true
|
||||||
|
}
|
||||||
|
if currentInstance.isFiltersSupported {
|
||||||
|
Label("Edit Filters", systemImage: "line.3.horizontal.decrease.circle")
|
||||||
|
.onTapGesture {
|
||||||
|
isEditingFilters = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
Section {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
if let token = appAccount.oauthToken {
|
||||||
|
Task {
|
||||||
|
await pushNotifications.deleteSubscriptions(accounts: [.init(server: appAccount.server,
|
||||||
|
token: token,
|
||||||
|
accountName: appAccount.accountName)])
|
||||||
|
appAccountsManager.delete(account: appAccount)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("Logout account")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $isEditingAccount, content: {
|
||||||
|
EditAccountView()
|
||||||
|
})
|
||||||
|
.sheet(isPresented: $isEditingFilters, content: {
|
||||||
|
FiltersListView()
|
||||||
|
})
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
HStack {
|
||||||
|
AvatarView(url: account.avatar, size: .embed)
|
||||||
|
Text(account.safeDisplayName)
|
||||||
|
.font(.headline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle(account.safeDisplayName)
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.background(theme.secondaryBackgroundColor)
|
||||||
|
}
|
||||||
|
}
|
@ -343,3 +343,13 @@
|
|||||||
"status.visibility.follower" = "Follower";
|
"status.visibility.follower" = "Follower";
|
||||||
"status.visibility.public" = "Öffentlich";
|
"status.visibility.public" = "Öffentlich";
|
||||||
"status.visibility.unlisted" = "Ungelistet";
|
"status.visibility.unlisted" = "Ungelistet";
|
||||||
|
|
||||||
|
// MARK: Filters
|
||||||
|
"filter.new" = "New Filter";
|
||||||
|
"filter.filters" = "Filters";
|
||||||
|
"filter.edit.title" = "Filter title";
|
||||||
|
"filter.edit.keywords" = "Filter Keywords";
|
||||||
|
"filter.edit.keywords.add" = "Add a new keyword";
|
||||||
|
"filter.edit.contexts" = "Filter Contexts";
|
||||||
|
"filter.edit.action" = "Filter Action";
|
||||||
|
"account.action.edit-filters" = "Edit Filters";
|
||||||
|
@ -346,3 +346,13 @@
|
|||||||
"status.visibility.follower" = "Followers";
|
"status.visibility.follower" = "Followers";
|
||||||
"status.visibility.public" = "Everyone";
|
"status.visibility.public" = "Everyone";
|
||||||
"status.visibility.unlisted" = "Unlisted";
|
"status.visibility.unlisted" = "Unlisted";
|
||||||
|
|
||||||
|
// MARK: Filters
|
||||||
|
"filter.new" = "New Filter";
|
||||||
|
"filter.filters" = "Filters";
|
||||||
|
"filter.edit.title" = "Filter title";
|
||||||
|
"filter.edit.keywords" = "Filter Keywords";
|
||||||
|
"filter.edit.keywords.add" = "Add a new keyword";
|
||||||
|
"filter.edit.contexts" = "Filter Contexts";
|
||||||
|
"filter.edit.action" = "Filter Action";
|
||||||
|
"account.action.edit-filters" = "Edit Filters";
|
||||||
|
@ -346,3 +346,13 @@
|
|||||||
"status.visibility.follower" = "Sólo seguidores";
|
"status.visibility.follower" = "Sólo seguidores";
|
||||||
"status.visibility.public" = "Todo el mundo";
|
"status.visibility.public" = "Todo el mundo";
|
||||||
"status.visibility.unlisted" = "Sin listar";
|
"status.visibility.unlisted" = "Sin listar";
|
||||||
|
|
||||||
|
// MARK: Filters
|
||||||
|
"filter.new" = "New Filter";
|
||||||
|
"filter.filters" = "Filters";
|
||||||
|
"filter.edit.title" = "Filter title";
|
||||||
|
"filter.edit.keywords" = "Filter Keywords";
|
||||||
|
"filter.edit.keywords.add" = "Add a new keyword";
|
||||||
|
"filter.edit.contexts" = "Filter Contexts";
|
||||||
|
"filter.edit.action" = "Filter Action";
|
||||||
|
"account.action.edit-filters" = "Edit Filters";
|
||||||
|
@ -343,3 +343,13 @@
|
|||||||
"status.visibility.follower" = "Solo ai follower";
|
"status.visibility.follower" = "Solo ai follower";
|
||||||
"status.visibility.public" = "A tutti";
|
"status.visibility.public" = "A tutti";
|
||||||
"status.visibility.unlisted" = "Ai non appartenenti alle liste";
|
"status.visibility.unlisted" = "Ai non appartenenti alle liste";
|
||||||
|
|
||||||
|
// MARK: Filters
|
||||||
|
"filter.new" = "New Filter";
|
||||||
|
"filter.filters" = "Filters";
|
||||||
|
"filter.edit.title" = "Filter title";
|
||||||
|
"filter.edit.keywords" = "Filter Keywords";
|
||||||
|
"filter.edit.keywords.add" = "Add a new keyword";
|
||||||
|
"filter.edit.contexts" = "Filter Contexts";
|
||||||
|
"filter.edit.action" = "Filter Action";
|
||||||
|
"account.action.edit-filters" = "Edit Filters";
|
||||||
|
@ -338,3 +338,14 @@
|
|||||||
"settings.content.default-visibility" = "投稿の可視化";
|
"settings.content.default-visibility" = "投稿の可視化";
|
||||||
"settings.content.reading" = "Reading";
|
"settings.content.reading" = "Reading";
|
||||||
"settings.content.posting" = "Posting";
|
"settings.content.posting" = "Posting";
|
||||||
|
|
||||||
|
// MARK: Filters
|
||||||
|
"filter.new" = "New Filter";
|
||||||
|
"filter.filters" = "Filters";
|
||||||
|
"filter.edit.title" = "Filter title";
|
||||||
|
"filter.edit.keywords" = "Filter Keywords";
|
||||||
|
"filter.edit.keywords.add" = "Add a new keyword";
|
||||||
|
"filter.edit.contexts" = "Filter Contexts";
|
||||||
|
"filter.edit.action" = "Filter Action";
|
||||||
|
"account.action.edit-filters" = "Edit Filters";
|
||||||
|
"account.action.edit-filters" = "Edit Filters";
|
||||||
|
@ -343,3 +343,13 @@
|
|||||||
"status.visibility.follower" = "Alleen volgers";
|
"status.visibility.follower" = "Alleen volgers";
|
||||||
"status.visibility.public" = "Openbaar";
|
"status.visibility.public" = "Openbaar";
|
||||||
"status.visibility.unlisted" = "Minder openbaar";
|
"status.visibility.unlisted" = "Minder openbaar";
|
||||||
|
|
||||||
|
// MARK: Filters
|
||||||
|
"filter.new" = "New Filter";
|
||||||
|
"filter.filters" = "Filters";
|
||||||
|
"filter.edit.title" = "Filter title";
|
||||||
|
"filter.edit.keywords" = "Filter Keywords";
|
||||||
|
"filter.edit.keywords.add" = "Add a new keyword";
|
||||||
|
"filter.edit.contexts" = "Filter Contexts";
|
||||||
|
"filter.edit.action" = "Filter Action";
|
||||||
|
"account.action.edit-filters" = "Edit Filters";
|
||||||
|
@ -329,3 +329,13 @@
|
|||||||
"status.visibility.follower" = "Takipçiler";
|
"status.visibility.follower" = "Takipçiler";
|
||||||
"status.visibility.public" = "Herkes";
|
"status.visibility.public" = "Herkes";
|
||||||
"status.visibility.unlisted" = "Liste dışı";
|
"status.visibility.unlisted" = "Liste dışı";
|
||||||
|
|
||||||
|
// MARK: Filters
|
||||||
|
"filter.new" = "New Filter";
|
||||||
|
"filter.filters" = "Filters";
|
||||||
|
"filter.edit.title" = "Filter title";
|
||||||
|
"filter.edit.keywords" = "Filter Keywords";
|
||||||
|
"filter.edit.keywords.add" = "Add a new keyword";
|
||||||
|
"filter.edit.contexts" = "Filter Contexts";
|
||||||
|
"filter.edit.action" = "Filter Action";
|
||||||
|
"account.action.edit-filters" = "Edit Filters";
|
||||||
|
@ -344,3 +344,13 @@
|
|||||||
"status.visibility.follower" = "粉丝";
|
"status.visibility.follower" = "粉丝";
|
||||||
"status.visibility.public" = "所有人";
|
"status.visibility.public" = "所有人";
|
||||||
"status.visibility.unlisted" = "不公开";
|
"status.visibility.unlisted" = "不公开";
|
||||||
|
|
||||||
|
// MARK: Filters
|
||||||
|
"filter.new" = "New Filter";
|
||||||
|
"filter.filters" = "Filters";
|
||||||
|
"filter.edit.title" = "Filter title";
|
||||||
|
"filter.edit.keywords" = "Filter Keywords";
|
||||||
|
"filter.edit.keywords.add" = "Add a new keyword";
|
||||||
|
"filter.edit.contexts" = "Filter Contexts";
|
||||||
|
"filter.edit.action" = "Filter Action";
|
||||||
|
"account.action.edit-filters" = "Edit Filters";
|
||||||
|
@ -13,6 +13,7 @@ public struct AccountDetailView: View {
|
|||||||
|
|
||||||
@EnvironmentObject private var watcher: StreamWatcher
|
@EnvironmentObject private var watcher: StreamWatcher
|
||||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||||
|
@EnvironmentObject private var curretnInstance: CurrentInstance
|
||||||
@EnvironmentObject private var preferences: UserPreferences
|
@EnvironmentObject private var preferences: UserPreferences
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
@EnvironmentObject private var client: Client
|
@EnvironmentObject private var client: Client
|
||||||
@ -25,6 +26,7 @@ public struct AccountDetailView: View {
|
|||||||
@State private var isCreateListAlertPresented: Bool = false
|
@State private var isCreateListAlertPresented: Bool = false
|
||||||
@State private var createListTitle: String = ""
|
@State private var createListTitle: String = ""
|
||||||
@State private var isEditingAccount: Bool = false
|
@State private var isEditingAccount: Bool = false
|
||||||
|
@State private var isEditingFilters: Bool = false
|
||||||
|
|
||||||
/// When coming from a URL like a mention tap in a status.
|
/// When coming from a URL like a mention tap in a status.
|
||||||
public init(accountId: String) {
|
public init(accountId: String) {
|
||||||
@ -121,6 +123,9 @@ public struct AccountDetailView: View {
|
|||||||
.sheet(isPresented: $isEditingAccount, content: {
|
.sheet(isPresented: $isEditingAccount, content: {
|
||||||
EditAccountView()
|
EditAccountView()
|
||||||
})
|
})
|
||||||
|
.sheet(isPresented: $isEditingFilters, content: {
|
||||||
|
FiltersListView()
|
||||||
|
})
|
||||||
.edgesIgnoringSafeArea(.top)
|
.edgesIgnoringSafeArea(.top)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
@ -507,6 +512,14 @@ public struct AccountDetailView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label("account.action.edit-info", systemImage: "pencil")
|
Label("account.action.edit-info", systemImage: "pencil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if curretnInstance.isFiltersSupported {
|
||||||
|
Button {
|
||||||
|
isEditingFilters = true
|
||||||
|
} label: {
|
||||||
|
Label("account.action.edit-filters", systemImage: "line.3.horizontal.decrease.circle")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,14 @@ import Models
|
|||||||
import Network
|
import Network
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct EditAccountView: View {
|
public struct EditAccountView: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@EnvironmentObject private var client: Client
|
@EnvironmentObject private var client: Client
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
@StateObject private var viewModel = EditAccountViewModel()
|
@StateObject private var viewModel = EditAccountViewModel()
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
216
Packages/Account/Sources/Account/Filters/EditFilterView.swift
Normal file
216
Packages/Account/Sources/Account/Filters/EditFilterView.swift
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import Models
|
||||||
|
import Env
|
||||||
|
import DesignSystem
|
||||||
|
import Network
|
||||||
|
|
||||||
|
struct EditFilterView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
@EnvironmentObject private var theme: Theme
|
||||||
|
@EnvironmentObject private var account: CurrentAccount
|
||||||
|
@EnvironmentObject private var client: Client
|
||||||
|
|
||||||
|
@State private var isSavingFilter: Bool = false
|
||||||
|
@State private var filter: ServerFilter?
|
||||||
|
@State private var title: String
|
||||||
|
@State private var keywords: [ServerFilter.Keyword]
|
||||||
|
@State private var newKeyword: String = ""
|
||||||
|
@State private var contexts: [ServerFilter.Context]
|
||||||
|
@State private var filterAction: ServerFilter.Action
|
||||||
|
|
||||||
|
@FocusState private var isTitleFocused: Bool
|
||||||
|
|
||||||
|
private var data: ServerFilterData {
|
||||||
|
.init(title: title,
|
||||||
|
context: contexts,
|
||||||
|
filterAction: filterAction,
|
||||||
|
expireIn: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var canSave: Bool {
|
||||||
|
!title.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
init(filter: ServerFilter?) {
|
||||||
|
_filter = .init(initialValue: filter)
|
||||||
|
_title = .init(initialValue: filter?.title ?? "")
|
||||||
|
_keywords = .init(initialValue: filter?.keywords ?? [])
|
||||||
|
_contexts = .init(initialValue: filter?.context ?? [.home])
|
||||||
|
_filterAction = .init(initialValue: filter?.filterAction ?? .warn)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
titleSection
|
||||||
|
if filter != nil {
|
||||||
|
keywordsSection
|
||||||
|
contextsSection
|
||||||
|
filterActionView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle(filter?.title ?? NSLocalizedString("filter.new", comment: ""))
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.background(theme.secondaryBackgroundColor)
|
||||||
|
.onAppear {
|
||||||
|
if filter == nil {
|
||||||
|
isTitleFocused = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
saveButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var titleSection: some View {
|
||||||
|
Section("filter.edit.title") {
|
||||||
|
TextField("filter.edit.title", text: $title)
|
||||||
|
.focused($isTitleFocused)
|
||||||
|
.onSubmit {
|
||||||
|
Task {
|
||||||
|
await saveFilter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private var keywordsSection: some View {
|
||||||
|
Section("filter.edit.keywords") {
|
||||||
|
ForEach(keywords) { keyword in
|
||||||
|
HStack {
|
||||||
|
Text(keyword.keyword)
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
await deleteKeyword(keyword: keyword)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "trash")
|
||||||
|
.tint(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDelete { indexes in
|
||||||
|
if let index = indexes.first {
|
||||||
|
let keyword = keywords[index]
|
||||||
|
Task {
|
||||||
|
await deleteKeyword(keyword: keyword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextField("filter.edit.keywords.add", text: $newKeyword, axis: .horizontal)
|
||||||
|
.onSubmit {
|
||||||
|
Task {
|
||||||
|
await addKeyword(name: newKeyword)
|
||||||
|
newKeyword = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var contextsSection: some View {
|
||||||
|
Section("filter.edit.contexts") {
|
||||||
|
ForEach(ServerFilter.Context.allCases, id: \.self) { context in
|
||||||
|
Toggle(isOn: .init(get: {
|
||||||
|
contexts.contains(where: { $0 == context })
|
||||||
|
}, set: { _ in
|
||||||
|
if let index = contexts.firstIndex(of: context) {
|
||||||
|
contexts.remove(at: index)
|
||||||
|
} else {
|
||||||
|
contexts.append(context)
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
await saveFilter()
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
Label(context.name, systemImage: context.iconName)
|
||||||
|
}
|
||||||
|
.disabled(isSavingFilter)
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var filterActionView: some View {
|
||||||
|
Section("filter.edit.action") {
|
||||||
|
Picker(selection: $filterAction) {
|
||||||
|
ForEach(ServerFilter.Action.allCases, id: \.self) { filter in
|
||||||
|
Text(filter.label)
|
||||||
|
.id(filter)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
.onChange(of: filterAction) { _ in
|
||||||
|
Task {
|
||||||
|
await saveFilter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.inline)
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var saveButton: some View {
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
await saveFilter()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
if isSavingFilter {
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
|
Text("action.done")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(!canSave)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func saveFilter() async {
|
||||||
|
do {
|
||||||
|
isSavingFilter = true
|
||||||
|
if let filter {
|
||||||
|
self.filter = try await client.put(endpoint: ServerFilters.editFilter(id: filter.id, json: data),
|
||||||
|
forceVersion: .v2)
|
||||||
|
} else {
|
||||||
|
let newFilter: ServerFilter = try await client.post(endpoint: ServerFilters.createFilter(json: data),
|
||||||
|
forceVersion: .v2)
|
||||||
|
self.filter = newFilter
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
isSavingFilter = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addKeyword(name: String) async {
|
||||||
|
guard let filterId = filter?.id else { return }
|
||||||
|
isSavingFilter = true
|
||||||
|
do {
|
||||||
|
let keyword: ServerFilter.Keyword = try await
|
||||||
|
client.post(endpoint: ServerFilters.addKeyword(filter: filterId,
|
||||||
|
keyword: name,
|
||||||
|
wholeWord: true),
|
||||||
|
forceVersion: .v2)
|
||||||
|
self.keywords.append(keyword)
|
||||||
|
} catch { }
|
||||||
|
isSavingFilter = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func deleteKeyword(keyword: ServerFilter.Keyword) async {
|
||||||
|
isSavingFilter = true
|
||||||
|
do {
|
||||||
|
let response = try await client.delete(endpoint: ServerFilters.removeKeyword(id: keyword.id),
|
||||||
|
forceVersion: .v2)
|
||||||
|
if response?.statusCode == 200 {
|
||||||
|
keywords.removeAll(where: { $0.id == keyword.id })
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
isSavingFilter = false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import Env
|
||||||
|
import Network
|
||||||
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
|
||||||
|
public struct FiltersListView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
@EnvironmentObject private var theme: Theme
|
||||||
|
@EnvironmentObject private var account: CurrentAccount
|
||||||
|
@EnvironmentObject private var client: Client
|
||||||
|
|
||||||
|
@State private var isLoading: Bool = true
|
||||||
|
@State private var filters: [ServerFilter] = []
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
if isLoading && filters.isEmpty {
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
|
ForEach(filters) { filter in
|
||||||
|
NavigationLink(destination: EditFilterView(filter: filter)) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(filter.title)
|
||||||
|
.font(.scaledSubheadline)
|
||||||
|
Text("\(filter.context.map{ $0.name }.joined(separator: ", "))")
|
||||||
|
.font(.scaledBody)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDelete { indexes in
|
||||||
|
deleteFilter(indexes: indexes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
|
Section {
|
||||||
|
NavigationLink(destination: EditFilterView(filter: nil)) {
|
||||||
|
Label("filter.new", systemImage: "plus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
toolbarContent
|
||||||
|
}
|
||||||
|
.navigationTitle("filter.filters")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.background(theme.secondaryBackgroundColor)
|
||||||
|
.task {
|
||||||
|
do {
|
||||||
|
isLoading = true
|
||||||
|
filters = try await client.get(endpoint: ServerFilters.filters, forceVersion: .v2)
|
||||||
|
isLoading = false
|
||||||
|
} catch {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func deleteFilter(indexes: IndexSet) {
|
||||||
|
if let index = indexes.first {
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
let response = try await client.delete(endpoint: ServerFilters.filter(id: filters[index].id),
|
||||||
|
forceVersion: .v2)
|
||||||
|
if response?.statusCode == 200 {
|
||||||
|
filters.remove(at: index)
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ToolbarContentBuilder
|
||||||
|
private var toolbarContent: some ToolbarContent {
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
Button("action.done") {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,15 +4,7 @@ import Models
|
|||||||
import Network
|
import Network
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public struct AppAccount: Codable, Identifiable {
|
extension AppAccount {
|
||||||
public let server: String
|
|
||||||
public var accountName: String?
|
|
||||||
public let oauthToken: OauthToken?
|
|
||||||
|
|
||||||
public var id: String {
|
|
||||||
key
|
|
||||||
}
|
|
||||||
|
|
||||||
private static var keychain: KeychainSwift {
|
private static var keychain: KeychainSwift {
|
||||||
let keychain = KeychainSwift()
|
let keychain = KeychainSwift()
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@ -21,23 +13,6 @@ public struct AppAccount: Codable, Identifiable {
|
|||||||
return keychain
|
return keychain
|
||||||
}
|
}
|
||||||
|
|
||||||
public var key: String {
|
|
||||||
if let oauthToken {
|
|
||||||
return "\(server):\(oauthToken.createdAt)"
|
|
||||||
} else {
|
|
||||||
return "\(server):anonymous:\(Date().timeIntervalSince1970)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(server: String,
|
|
||||||
accountName: String?,
|
|
||||||
oauthToken: OauthToken? = nil)
|
|
||||||
{
|
|
||||||
self.server = server
|
|
||||||
self.accountName = accountName
|
|
||||||
self.oauthToken = oauthToken
|
|
||||||
}
|
|
||||||
|
|
||||||
public func save() throws {
|
public func save() throws {
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
let data = try encoder.encode(self)
|
let data = try encoder.encode(self)
|
||||||
|
@ -64,7 +64,7 @@ public struct AppAccountView: View {
|
|||||||
if appAccounts.currentAccount.id == viewModel.appAccount.id,
|
if appAccounts.currentAccount.id == viewModel.appAccount.id,
|
||||||
let account = viewModel.account
|
let account = viewModel.account
|
||||||
{
|
{
|
||||||
routerPath.navigate(to: .accountDetailWithAccount(account: account))
|
routerPath.navigate(to: .accountSettingsWithAccount(account: account, appAccount: viewModel.appAccount))
|
||||||
} else {
|
} else {
|
||||||
appAccounts.currentAccount = viewModel.appAccount
|
appAccounts.currentAccount = viewModel.appAccount
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,14 @@ public class CurrentInstance: ObservableObject {
|
|||||||
private var client: Client?
|
private var client: Client?
|
||||||
|
|
||||||
public static let shared = CurrentInstance()
|
public static let shared = CurrentInstance()
|
||||||
|
|
||||||
|
public var isFiltersSupported: Bool {
|
||||||
|
instance?.version.hasPrefix("4") == true
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isEditSupported: Bool {
|
||||||
|
instance?.version.hasPrefix("4") == true
|
||||||
|
}
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import SwiftUI
|
|||||||
public enum RouterDestinations: Hashable {
|
public enum RouterDestinations: Hashable {
|
||||||
case accountDetail(id: String)
|
case accountDetail(id: String)
|
||||||
case accountDetailWithAccount(account: Account)
|
case accountDetailWithAccount(account: Account)
|
||||||
|
case accountSettingsWithAccount(account: Account, appAccount: AppAccount)
|
||||||
case statusDetail(id: String)
|
case statusDetail(id: String)
|
||||||
case conversationDetail(conversation: Conversation)
|
case conversationDetail(conversation: Conversation)
|
||||||
case remoteStatusDetail(url: URL)
|
case remoteStatusDetail(url: URL)
|
||||||
|
28
Packages/Models/Sources/Models/AppAccount.swift
Normal file
28
Packages/Models/Sources/Models/AppAccount.swift
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct AppAccount: Codable, Identifiable, Hashable {
|
||||||
|
public let server: String
|
||||||
|
public var accountName: String?
|
||||||
|
public let oauthToken: OauthToken?
|
||||||
|
|
||||||
|
public var key: String {
|
||||||
|
if let oauthToken {
|
||||||
|
return "\(server):\(oauthToken.createdAt)"
|
||||||
|
} else {
|
||||||
|
return "\(server):anonymous:\(Date().timeIntervalSince1970)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var id: String {
|
||||||
|
key
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(server: String,
|
||||||
|
accountName: String?,
|
||||||
|
oauthToken: OauthToken? = nil) {
|
||||||
|
self.server = server
|
||||||
|
self.accountName = accountName
|
||||||
|
self.oauthToken = oauthToken
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct OauthToken: Codable {
|
public struct OauthToken: Codable, Hashable {
|
||||||
public let accessToken: String
|
public let accessToken: String
|
||||||
public let tokenType: String
|
public let tokenType: String
|
||||||
public let scope: String
|
public let scope: String
|
||||||
|
67
Packages/Models/Sources/Models/ServerFilter.swift
Normal file
67
Packages/Models/Sources/Models/ServerFilter.swift
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct ServerFilter: Codable, Identifiable, Hashable {
|
||||||
|
public struct Keyword: Codable, Identifiable, Hashable {
|
||||||
|
public let id: String
|
||||||
|
public let keyword: String
|
||||||
|
public let wholeWord: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Context: String, Codable, CaseIterable {
|
||||||
|
case home, notifications, `public`, thread, account
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Action: String, Codable, CaseIterable {
|
||||||
|
case warn, hide
|
||||||
|
}
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public let title: String
|
||||||
|
public let keywords: [Keyword]
|
||||||
|
public let filterAction: Action
|
||||||
|
public let context: [Context]
|
||||||
|
public let expireIn: Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ServerFilter.Context {
|
||||||
|
public var iconName: String {
|
||||||
|
switch self {
|
||||||
|
case .home:
|
||||||
|
return "rectangle.on.rectangle"
|
||||||
|
case .notifications:
|
||||||
|
return "bell"
|
||||||
|
case .public:
|
||||||
|
return "globe.americas"
|
||||||
|
case .thread:
|
||||||
|
return "bubble.left.and.bubble.right"
|
||||||
|
case .account:
|
||||||
|
return "person.crop.circle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var name: String {
|
||||||
|
switch self {
|
||||||
|
case .home:
|
||||||
|
return "Home and lists"
|
||||||
|
case .notifications:
|
||||||
|
return "Notifications"
|
||||||
|
case .public:
|
||||||
|
return "Public timelines"
|
||||||
|
case .thread:
|
||||||
|
return "Conversations"
|
||||||
|
case .account:
|
||||||
|
return "Profiles"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ServerFilter.Action {
|
||||||
|
public var label: String {
|
||||||
|
switch self {
|
||||||
|
case .warn:
|
||||||
|
return "Hide with a warning"
|
||||||
|
case .hide:
|
||||||
|
return "Hide completely"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -107,12 +107,12 @@ public class Client: ObservableObject, Equatable {
|
|||||||
return (try decoder.decode(Entity.self, from: data), linkHandler)
|
return (try decoder.decode(Entity.self, from: data), linkHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func post<Entity: Decodable>(endpoint: Endpoint) async throws -> Entity {
|
public func post<Entity: Decodable>(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> Entity {
|
||||||
try await makeEntityRequest(endpoint: endpoint, method: "POST")
|
try await makeEntityRequest(endpoint: endpoint, method: "POST", forceVersion: forceVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func post(endpoint: Endpoint) async throws -> HTTPURLResponse? {
|
public func post(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> HTTPURLResponse? {
|
||||||
let url = makeURL(endpoint: endpoint)
|
let url = makeURL(endpoint: endpoint, forceVersion: forceVersion)
|
||||||
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "POST")
|
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "POST")
|
||||||
let (_, httpResponse) = try await urlSession.data(for: request)
|
let (_, httpResponse) = try await urlSession.data(for: request)
|
||||||
return httpResponse as? HTTPURLResponse
|
return httpResponse as? HTTPURLResponse
|
||||||
@ -125,12 +125,12 @@ public class Client: ObservableObject, Equatable {
|
|||||||
return httpResponse as? HTTPURLResponse
|
return httpResponse as? HTTPURLResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
public func put<Entity: Decodable>(endpoint: Endpoint) async throws -> Entity {
|
public func put<Entity: Decodable>(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> Entity {
|
||||||
try await makeEntityRequest(endpoint: endpoint, method: "PUT")
|
try await makeEntityRequest(endpoint: endpoint, method: "PUT", forceVersion: forceVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func delete(endpoint: Endpoint) async throws -> HTTPURLResponse? {
|
public func delete(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> HTTPURLResponse? {
|
||||||
let url = makeURL(endpoint: endpoint)
|
let url = makeURL(endpoint: endpoint, forceVersion: forceVersion)
|
||||||
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "DELETE")
|
let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "DELETE")
|
||||||
let (_, httpResponse) = try await urlSession.data(for: request)
|
let (_, httpResponse) = try await urlSession.data(for: request)
|
||||||
return httpResponse as? HTTPURLResponse
|
return httpResponse as? HTTPURLResponse
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
import Foundation
|
||||||
|
import Models
|
||||||
|
|
||||||
|
public enum ServerFilters: Endpoint {
|
||||||
|
case filters
|
||||||
|
case createFilter(json: ServerFilterData)
|
||||||
|
case editFilter(id: String, json: ServerFilterData)
|
||||||
|
case addKeyword(filter: String, keyword: String, wholeWord: Bool)
|
||||||
|
case removeKeyword(id: String)
|
||||||
|
case filter(id: String)
|
||||||
|
|
||||||
|
public func path() -> String {
|
||||||
|
switch self {
|
||||||
|
case .filters:
|
||||||
|
return "filters"
|
||||||
|
case .createFilter:
|
||||||
|
return "filters"
|
||||||
|
case let .filter(id):
|
||||||
|
return "filters/\(id)"
|
||||||
|
case let .editFilter(id, _):
|
||||||
|
return "filters/\(id)"
|
||||||
|
case let .addKeyword(id, _, _):
|
||||||
|
return "filters/\(id)/keywords"
|
||||||
|
case let .removeKeyword(id):
|
||||||
|
return "filters/keywords/\(id)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
|
switch self {
|
||||||
|
case let .addKeyword(_, keyword, wholeWord):
|
||||||
|
return [.init(name: "keyword", value: keyword),
|
||||||
|
.init(name: "whole_word", value: wholeWord ? "true": "false")]
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var jsonValue: Encodable? {
|
||||||
|
switch self {
|
||||||
|
case let .createFilter(json):
|
||||||
|
return json
|
||||||
|
case let .editFilter(_, json):
|
||||||
|
return json
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ServerFilterData: Encodable {
|
||||||
|
public let title: String
|
||||||
|
public let context: [ServerFilter.Context]
|
||||||
|
public let filterAction: ServerFilter.Action
|
||||||
|
public let expireIn: Int?
|
||||||
|
|
||||||
|
public init(title: String,
|
||||||
|
context: [ServerFilter.Context],
|
||||||
|
filterAction: ServerFilter.Action,
|
||||||
|
expireIn: Int?) {
|
||||||
|
self.title = title
|
||||||
|
self.context = context
|
||||||
|
self.filterAction = filterAction
|
||||||
|
self.expireIn = expireIn
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import SwiftUI
|
|||||||
struct StatusRowContextMenu: View {
|
struct StatusRowContextMenu: View {
|
||||||
@EnvironmentObject private var preferences: UserPreferences
|
@EnvironmentObject private var preferences: UserPreferences
|
||||||
@EnvironmentObject private var account: CurrentAccount
|
@EnvironmentObject private var account: CurrentAccount
|
||||||
|
@EnvironmentObject private var currentInstance: CurrentInstance
|
||||||
@EnvironmentObject private var routerPath: RouterPath
|
@EnvironmentObject private var routerPath: RouterPath
|
||||||
|
|
||||||
@Environment(\.openURL) var openURL
|
@Environment(\.openURL) var openURL
|
||||||
@ -101,10 +102,12 @@ struct StatusRowContextMenu: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label(viewModel.isPinned ? "status.action.unpin" : "status.action.pin", systemImage: viewModel.isPinned ? "pin.fill" : "pin")
|
Label(viewModel.isPinned ? "status.action.unpin" : "status.action.pin", systemImage: viewModel.isPinned ? "pin.fill" : "pin")
|
||||||
}
|
}
|
||||||
Button {
|
if currentInstance.isEditSupported {
|
||||||
routerPath.presentedSheet = .editStatusEditor(status: viewModel.status)
|
Button {
|
||||||
} label: {
|
routerPath.presentedSheet = .editStatusEditor(status: viewModel.status)
|
||||||
Label("status.action.edit", systemImage: "pencil")
|
} label: {
|
||||||
|
Label("status.action.edit", systemImage: "pencil")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Button(role: .destructive) { Task { await viewModel.delete() } } label: {
|
Button(role: .destructive) { Task { await viewModel.delete() } } label: {
|
||||||
Label("status.action.delete", systemImage: "trash")
|
Label("status.action.delete", systemImage: "trash")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user