Merge pull request #35 from am7590/settings-bug
Settings bug fix + reorganization
This commit is contained in:
commit
3fa7c56942
7 changed files with 445 additions and 360 deletions
60
ntfy/Views/Settings/AboutView.swift
Normal file
60
ntfy/Views/Settings/AboutView.swift
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// AboutView.swift
|
||||
// ntfy
|
||||
//
|
||||
// Created by Alek Michelson on 4/10/26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AboutView: View {
|
||||
var body: some View {
|
||||
Group {
|
||||
Button(action: {
|
||||
open(url: "https://ntfy.sh/docs")
|
||||
}) {
|
||||
HStack {
|
||||
Text("Read the docs")
|
||||
Spacer()
|
||||
Text("ntfy.sh/docs")
|
||||
.foregroundColor(.gray)
|
||||
Image(systemName: "link")
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
open(url: "https://github.com/binwiederhier/ntfy/issues")
|
||||
}) {
|
||||
HStack {
|
||||
Text("Report a bug")
|
||||
Spacer()
|
||||
Text("github.com")
|
||||
.foregroundColor(.gray)
|
||||
Image(systemName: "link")
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
open(url: "itms-apps://itunes.apple.com/app/id1625396347")
|
||||
}) {
|
||||
HStack {
|
||||
Text("Rate the app")
|
||||
Spacer()
|
||||
Text("App Store")
|
||||
.foregroundColor(.gray)
|
||||
Image(systemName: "star.fill")
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Text("Version")
|
||||
Spacer()
|
||||
Text("ntfy \(Config.version) (\(Config.build))")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
|
||||
private func open(url: String) {
|
||||
guard let url = URL(string: url) else { return }
|
||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
104
ntfy/Views/Settings/DefaultServerView.swift
Normal file
104
ntfy/Views/Settings/DefaultServerView.swift
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
//
|
||||
// DefaultServerView.swift
|
||||
// ntfy
|
||||
//
|
||||
// Created by Alek Michelson on 4/10/26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DefaultServerView: View {
|
||||
@EnvironmentObject private var store: Store
|
||||
@FetchRequest(sortDescriptors: []) var prefs: FetchedResults<Preference>
|
||||
@State private var showDialog = false
|
||||
@State private var newDefaultBaseUrl: String = ""
|
||||
|
||||
private var defaultBaseUrl: String {
|
||||
prefs
|
||||
.filter { $0.key == Store.prefKeyDefaultBaseUrl }
|
||||
.first?
|
||||
.value ?? Config.appBaseUrl
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: {
|
||||
if defaultBaseUrl == Config.appBaseUrl {
|
||||
newDefaultBaseUrl = ""
|
||||
} else {
|
||||
newDefaultBaseUrl = defaultBaseUrl
|
||||
}
|
||||
showDialog = true
|
||||
}) {
|
||||
HStack {
|
||||
let _ = newDefaultBaseUrl
|
||||
Text("Default server")
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Text(shortUrl(url: defaultBaseUrl))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.sheet(isPresented: $showDialog) {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section(
|
||||
footer: Text("When subscribing to new topics, this server will be used as a default. Note that if you pick your own ntfy server, you must configure upstream-base-url to receive instant push notifications.")
|
||||
) {
|
||||
HStack {
|
||||
TextField(Config.appBaseUrl, text: $newDefaultBaseUrl)
|
||||
.disableAutocapitalization()
|
||||
.disableAutocorrection(true)
|
||||
if !newDefaultBaseUrl.isEmpty {
|
||||
Button {
|
||||
newDefaultBaseUrl = ""
|
||||
} label: {
|
||||
Image(systemName: "clear.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Default server")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(action: cancelAction) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: saveAction) {
|
||||
Text("Save")
|
||||
}
|
||||
.disabled(!isValid())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveAction() {
|
||||
if newDefaultBaseUrl == "" {
|
||||
store.saveDefaultBaseUrl(baseUrl: nil)
|
||||
} else {
|
||||
store.saveDefaultBaseUrl(baseUrl: normalizeBaseUrl(newDefaultBaseUrl))
|
||||
}
|
||||
resetAndHide()
|
||||
}
|
||||
|
||||
private func cancelAction() {
|
||||
resetAndHide()
|
||||
}
|
||||
|
||||
private func isValid() -> Bool {
|
||||
if !newDefaultBaseUrl.isEmpty && newDefaultBaseUrl.range(of: "^https?://.+", options: .regularExpression, range: nil, locale: nil) == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func resetAndHide() {
|
||||
showDialog = false
|
||||
}
|
||||
}
|
||||
59
ntfy/Views/Settings/SettingsView.swift
Normal file
59
ntfy/Views/Settings/SettingsView.swift
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import StoreKit
|
||||
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject private var store: Store
|
||||
@State private var userDialog: UserDialog?
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section(
|
||||
header: Text("General"),
|
||||
footer: Text("When subscribing to new topics, this server will be used as a default.")
|
||||
) {
|
||||
DefaultServerView()
|
||||
}
|
||||
Section(
|
||||
header: Text("Users"),
|
||||
footer: Text("To access read-protected topics, you may add or edit users here. All topics for a given server will use the same user.")
|
||||
) {
|
||||
UserTableView(dialog: $userDialog)
|
||||
}
|
||||
Section(header: Text("About")) {
|
||||
AboutView()
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
}
|
||||
.sheet(item: $userDialog) { dialog in
|
||||
UserEditorView(
|
||||
selectedUser: dialog.user,
|
||||
onSave: { baseUrl, username, password in
|
||||
store.saveUser(baseUrl: baseUrl, username: username, password: password)
|
||||
userDialog = nil
|
||||
},
|
||||
onDelete: { user in
|
||||
store.delete(user: user)
|
||||
userDialog = nil
|
||||
},
|
||||
onCancel: {
|
||||
userDialog = nil
|
||||
}
|
||||
)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let store = Store.preview // Store.previewEmpty
|
||||
SettingsView()
|
||||
.environment(\.managedObjectContext, store.context)
|
||||
.environmentObject(store)
|
||||
.environmentObject(AppDelegate())
|
||||
}
|
||||
}
|
||||
129
ntfy/Views/Settings/UserEditorView.swift
Normal file
129
ntfy/Views/Settings/UserEditorView.swift
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
//
|
||||
// UserEditorView.swift
|
||||
// ntfy
|
||||
//
|
||||
// Created by Alek Michelson on 4/10/26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct UserEditorView: View {
|
||||
@EnvironmentObject private var store: Store
|
||||
|
||||
let selectedUser: User?
|
||||
let onSave: (String, String, String) -> Void
|
||||
let onDelete: (User) -> Void
|
||||
let onCancel: () -> Void
|
||||
|
||||
@State private var baseUrl: String
|
||||
@State private var username: String
|
||||
@State private var password: String
|
||||
|
||||
init(
|
||||
selectedUser: User?,
|
||||
onSave: @escaping (String, String, String) -> Void,
|
||||
onDelete: @escaping (User) -> Void,
|
||||
onCancel: @escaping () -> Void
|
||||
) {
|
||||
self.selectedUser = selectedUser
|
||||
self.onSave = onSave
|
||||
self.onDelete = onDelete
|
||||
self.onCancel = onCancel
|
||||
_baseUrl = State(initialValue: selectedUser?.baseUrl ?? "")
|
||||
_username = State(initialValue: selectedUser?.username ?? "")
|
||||
_password = State(initialValue: "")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section(
|
||||
footer: isNewUser
|
||||
? Text("You can add a user here. All topics for the given server will use this user.")
|
||||
: Text("Edit the username or password for \(shortUrl(url: baseUrl)) here. This user is used for all topics of this server. Leave the password blank to leave it unchanged.")
|
||||
) {
|
||||
if isNewUser {
|
||||
TextField("Service URL, e.g. https://ntfy.home.io", text: $baseUrl)
|
||||
.disableAutocapitalization()
|
||||
.disableAutocorrection(true)
|
||||
}
|
||||
TextField("Username", text: $username)
|
||||
.disableAutocapitalization()
|
||||
.disableAutocorrection(true)
|
||||
SecureField("Password", text: $password)
|
||||
}
|
||||
}
|
||||
.navigationTitle(isNewUser ? "Add user" : "Edit user")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
if isNewUser {
|
||||
Button("Cancel") {
|
||||
onCancel()
|
||||
}
|
||||
} else {
|
||||
Menu {
|
||||
Button("Cancel") {
|
||||
onCancel()
|
||||
}
|
||||
if #available(iOS 15.0, *) {
|
||||
Button(role: .destructive) {
|
||||
deleteAction()
|
||||
} label: {
|
||||
Text("Delete")
|
||||
}
|
||||
} else {
|
||||
Button("Delete") {
|
||||
deleteAction()
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
.padding([.leading], 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: saveAction) {
|
||||
Text("Save")
|
||||
}
|
||||
.disabled(!isValid())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var isNewUser: Bool {
|
||||
selectedUser == nil
|
||||
}
|
||||
|
||||
private func saveAction() {
|
||||
let finalPassword: String
|
||||
if let user = selectedUser, password.isEmpty {
|
||||
finalPassword = user.password ?? "?"
|
||||
} else {
|
||||
finalPassword = password
|
||||
}
|
||||
onSave(baseUrl, username, finalPassword)
|
||||
}
|
||||
|
||||
private func deleteAction() {
|
||||
guard let selectedUser = selectedUser else { return }
|
||||
onDelete(selectedUser)
|
||||
}
|
||||
|
||||
private func isValid() -> Bool {
|
||||
if isNewUser {
|
||||
if baseUrl.range(of: "^https?://.+", options: .regularExpression, range: nil, locale: nil) == nil {
|
||||
return false
|
||||
} else if username.isEmpty || password.isEmpty {
|
||||
return false
|
||||
} else if store.getUser(baseUrl: baseUrl) != nil {
|
||||
return false
|
||||
}
|
||||
} else if username.isEmpty {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
36
ntfy/Views/Settings/UserRowView.swift
Normal file
36
ntfy/Views/Settings/UserRowView.swift
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// UserRowView.swift
|
||||
// ntfy
|
||||
//
|
||||
// Created by Alek Michelson on 4/10/26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct UserRowView: View {
|
||||
@EnvironmentObject private var store: Store
|
||||
@ObservedObject var user: User
|
||||
|
||||
var body: some View {
|
||||
// TODO: swipe to delete action
|
||||
// I tried to add a swipe action here to delete, but for some strange reason it doesn't work,
|
||||
// even though in the subscription list it does.
|
||||
|
||||
HStack {
|
||||
Image(systemName: "person.fill")
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(user.username ?? "?")
|
||||
Text(user.baseUrl ?? "?")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
Image(systemName: "chevron.forward")
|
||||
.font(.system(size: 12.0))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.padding(.all, 4)
|
||||
}
|
||||
}
|
||||
57
ntfy/Views/Settings/UserTableView.swift
Normal file
57
ntfy/Views/Settings/UserTableView.swift
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// UserTableView.swift
|
||||
// ntfy
|
||||
//
|
||||
// Created by Alek Michelson on 4/10/26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum UserDialog: Identifiable {
|
||||
case add
|
||||
case edit(User)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .add:
|
||||
return "add"
|
||||
case .edit(let user):
|
||||
return user.objectID.uriRepresentation().absoluteString
|
||||
}
|
||||
}
|
||||
|
||||
var user: User? {
|
||||
switch self {
|
||||
case .add:
|
||||
return nil
|
||||
case .edit(let user):
|
||||
return user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UserTableView: View {
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \User.baseUrl, ascending: true)]) var users: FetchedResults<User>
|
||||
|
||||
@Binding var dialog: UserDialog?
|
||||
|
||||
var body: some View {
|
||||
ForEach(users) { user in
|
||||
Button(action: {
|
||||
dialog = .edit(user)
|
||||
}) {
|
||||
UserRowView(user: user)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
dialog = .add
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "plus")
|
||||
Text("Add user")
|
||||
}
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import StoreKit
|
||||
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject private var store: Store
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section(
|
||||
header: Text("General"),
|
||||
footer: Text("When subscribing to new topics, this server will be used as a default.")
|
||||
) {
|
||||
DefaultServerView()
|
||||
}
|
||||
Section(
|
||||
header: Text("Users"),
|
||||
footer: Text("To access read-protected topics, you may add or edit users here. All topics for a given server will use the same user.")
|
||||
) {
|
||||
UserTableView()
|
||||
}
|
||||
Section(header: Text("About")) {
|
||||
AboutView()
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct DefaultServerView: View {
|
||||
@EnvironmentObject private var store: Store
|
||||
@FetchRequest(sortDescriptors: []) var prefs: FetchedResults<Preference>
|
||||
@State private var showDialog = false
|
||||
@State private var newDefaultBaseUrl: String = ""
|
||||
|
||||
private var defaultBaseUrl: String {
|
||||
prefs
|
||||
.filter { $0.key == Store.prefKeyDefaultBaseUrl }
|
||||
.first?
|
||||
.value ?? Config.appBaseUrl
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: {
|
||||
if defaultBaseUrl == Config.appBaseUrl {
|
||||
newDefaultBaseUrl = ""
|
||||
} else {
|
||||
newDefaultBaseUrl = defaultBaseUrl
|
||||
}
|
||||
showDialog = true
|
||||
}) {
|
||||
HStack {
|
||||
let _ = newDefaultBaseUrl
|
||||
Text("Default server")
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Text(shortUrl(url: defaultBaseUrl))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.sheet(isPresented: $showDialog) {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section(
|
||||
footer: Text("When subscribing to new topics, this server will be used as a default. Note that if you pick your own ntfy server, you must configure upstream-base-url to receive instant push notifications.")
|
||||
) {
|
||||
HStack {
|
||||
TextField(Config.appBaseUrl, text: $newDefaultBaseUrl)
|
||||
.disableAutocapitalization()
|
||||
.disableAutocorrection(true)
|
||||
if !newDefaultBaseUrl.isEmpty {
|
||||
Button {
|
||||
newDefaultBaseUrl = ""
|
||||
} label: {
|
||||
Image(systemName: "clear.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Default server")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(action: cancelAction) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: saveAction) {
|
||||
Text("Save")
|
||||
}
|
||||
.disabled(!isValid())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveAction() {
|
||||
if newDefaultBaseUrl == "" {
|
||||
store.saveDefaultBaseUrl(baseUrl: nil)
|
||||
} else {
|
||||
store.saveDefaultBaseUrl(baseUrl: normalizeBaseUrl(newDefaultBaseUrl))
|
||||
}
|
||||
resetAndHide()
|
||||
}
|
||||
|
||||
private func cancelAction() {
|
||||
resetAndHide()
|
||||
}
|
||||
|
||||
private func isValid() -> Bool {
|
||||
if !newDefaultBaseUrl.isEmpty && newDefaultBaseUrl.range(of: "^https?://.+", options: .regularExpression, range: nil, locale: nil) == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func resetAndHide() {
|
||||
showDialog = false
|
||||
}
|
||||
}
|
||||
|
||||
struct UserTableView: View {
|
||||
@EnvironmentObject private var store: Store
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \User.baseUrl, ascending: true)]) var users: FetchedResults<User>
|
||||
|
||||
@State private var selectedUser: User?
|
||||
@State private var showDialog = false
|
||||
|
||||
@State private var baseUrl: String = ""
|
||||
@State private var username: String = ""
|
||||
@State private var password: String = ""
|
||||
|
||||
var body: some View {
|
||||
let _ = selectedUser?.username // Workaround for FB7823148, see https://developer.apple.com/forums/thread/652080
|
||||
List {
|
||||
ForEach(users) { user in
|
||||
Button(action: {
|
||||
selectedUser = user
|
||||
baseUrl = user.baseUrl ?? "?"
|
||||
username = user.username ?? "?"
|
||||
showDialog = true
|
||||
}) {
|
||||
UserRowView(user: user)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
showDialog = true
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "plus")
|
||||
Text("Add user")
|
||||
}
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.padding(.all, 4)
|
||||
}
|
||||
.sheet(isPresented: $showDialog) {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section(
|
||||
footer: (selectedUser == nil)
|
||||
? Text("You can add a user here. All topics for the given server will use this user.")
|
||||
: Text("Edit the username or password for \(shortUrl(url: baseUrl)) here. This user is used for all topics of this server. Leave the password blank to leave it unchanged.")
|
||||
) {
|
||||
if selectedUser == nil {
|
||||
TextField("Service URL, e.g. https://ntfy.home.io", text: $baseUrl)
|
||||
.disableAutocapitalization()
|
||||
.disableAutocorrection(true)
|
||||
}
|
||||
TextField("Username", text: $username)
|
||||
.disableAutocapitalization()
|
||||
.disableAutocorrection(true)
|
||||
SecureField("Password", text: $password)
|
||||
}
|
||||
}
|
||||
.navigationTitle(selectedUser == nil ? "Add user" : "Edit user")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
if selectedUser == nil {
|
||||
Button("Cancel") {
|
||||
cancelAction()
|
||||
}
|
||||
} else {
|
||||
Menu {
|
||||
Button("Cancel") {
|
||||
cancelAction()
|
||||
}
|
||||
if #available(iOS 15.0, *) {
|
||||
Button(role: .destructive) {
|
||||
deleteAction()
|
||||
} label: {
|
||||
Text("Delete")
|
||||
}
|
||||
} else {
|
||||
Button("Delete") {
|
||||
deleteAction()
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
.padding([.leading], 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: saveAction) {
|
||||
Text("Save")
|
||||
}
|
||||
.disabled(!isValid())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveAction() {
|
||||
var password = password
|
||||
if let user = selectedUser, password == "" {
|
||||
password = user.password ?? "?" // If password is blank, leave unchanged
|
||||
}
|
||||
store.saveUser(baseUrl: baseUrl, username: username, password: password)
|
||||
resetAndHide()
|
||||
}
|
||||
|
||||
private func cancelAction() {
|
||||
resetAndHide()
|
||||
}
|
||||
|
||||
private func deleteAction() {
|
||||
store.delete(user: selectedUser!)
|
||||
resetAndHide()
|
||||
}
|
||||
|
||||
private func isValid() -> Bool {
|
||||
if selectedUser == nil { // New user
|
||||
if baseUrl.range(of: "^https?://.+", options: .regularExpression, range: nil, locale: nil) == nil {
|
||||
return false
|
||||
} else if username.isEmpty || password.isEmpty {
|
||||
return false
|
||||
} else if store.getUser(baseUrl: baseUrl) != nil {
|
||||
return false
|
||||
}
|
||||
} else { // Existing user
|
||||
if username.isEmpty {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func resetAndHide() {
|
||||
showDialog = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
// Hide first and then reset, otherwise we'll see the text fields change
|
||||
selectedUser = nil
|
||||
baseUrl = ""
|
||||
username = ""
|
||||
password = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UserRowView: View {
|
||||
@EnvironmentObject private var store: Store
|
||||
@ObservedObject var user: User
|
||||
|
||||
var body: some View {
|
||||
// I tried to add a swipe action here to delete, but for some strange reason it doesn't work,
|
||||
// even though in the subscription list it does.
|
||||
|
||||
HStack {
|
||||
Image(systemName: "person.fill")
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(user.username ?? "?")
|
||||
Text(user.baseUrl ?? "?")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
Image(systemName: "chevron.forward")
|
||||
.font(.system(size: 12.0))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.padding(.all, 4)
|
||||
}
|
||||
}
|
||||
|
||||
struct AboutView: View {
|
||||
var body: some View {
|
||||
Group {
|
||||
Button(action: {
|
||||
open(url: "https://ntfy.sh/docs")
|
||||
}) {
|
||||
HStack {
|
||||
Text("Read the docs")
|
||||
Spacer()
|
||||
Text("ntfy.sh/docs")
|
||||
.foregroundColor(.gray)
|
||||
Image(systemName: "link")
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
open(url: "https://github.com/binwiederhier/ntfy/issues")
|
||||
}) {
|
||||
HStack {
|
||||
Text("Report a bug")
|
||||
Spacer()
|
||||
Text("github.com")
|
||||
.foregroundColor(.gray)
|
||||
Image(systemName: "link")
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
open(url: "itms-apps://itunes.apple.com/app/id1625396347")
|
||||
}) {
|
||||
HStack {
|
||||
Text("Rate the app")
|
||||
Spacer()
|
||||
Text("App Store")
|
||||
.foregroundColor(.gray)
|
||||
Image(systemName: "star.fill")
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Text("Version")
|
||||
Spacer()
|
||||
Text("ntfy \(Config.version) (\(Config.build))")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
|
||||
private func open(url: String) {
|
||||
guard let url = URL(string: url) else { return }
|
||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let store = Store.preview // Store.previewEmpty
|
||||
SettingsView()
|
||||
.environment(\.managedObjectContext, store.context)
|
||||
.environmentObject(store)
|
||||
.environmentObject(AppDelegate())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue