Files
andros 7e1c37db7f Settings toggle to disable per-post share + configurable preview URL
The previous commit hardcoded https://preview.org-social.org/ as the
preview service for the Share button on each post. Now both the toggle
and the URL are user-configurable:

- New UserDefaults keys, registered with sensible defaults in
  OrgSocialApp.init: previewServiceEnabled (true), previewServiceURL
  ("https://preview.org-social.org/").
- SettingsViewModel exposes both as @Observable properties, persists them
  in saveIfValid, and adds previewServiceValid -> canSave so the URL
  must be a valid HTTP(S) URL when the toggle is on (when off the field
  is irrelevant and validation passes).
- SettingsView gets a new "Sharing" section between Relay and
  Migration, with a Toggle and a conditionally-shown Preview URL field.
  Field disappears entirely when the toggle is off.
- PostRowView reads previewServiceEnabled at render time, hiding the
  ShareLink when off, and reads previewServiceURL when building the
  share URL (tolerates trailing slash both ways).

Bundle behaviour: existing installs default to "on" + the public preview
service, so nothing changes for current users until they opt out.
2026-04-26 08:00:28 +02:00

158 lines
6.4 KiB
Swift

import Foundation
import Observation
import OrgSocialKit
@Observable @MainActor
final class SettingsViewModel {
// Common
var uploadMethod: UploadMethod
var publicFeedURL: String
var relayURL: String
var maxPostAgeDays: Int
var useRelay: Bool
var showAllRelayFeeds: Bool
// Sharing preview service used by the per-post Share button.
var previewServiceEnabled: Bool
var previewServiceURL: String
// VFile
var vfileURL: String
// GitHub
var githubToken: String
var githubOwner: String
var githubRepo: String
var githubPath: String
var githubBranch: String
// Codeberg
var codebergToken: String
var codebergOwner: String
var codebergRepo: String
var codebergPath: String
var codebergBranch: String
var codebergInstance: String
// WebDAV
var webdavURL: String
var webdavUsername: String
var webdavPassword: String
private let d = UserDefaults.standard
init() {
uploadMethod = UploadMethod(rawValue: UserDefaults.standard.string(forKey: "uploadMethod") ?? "") ?? .vfile
publicFeedURL = UserDefaults.standard.string(forKey: "publicFeedURL") ?? ""
relayURL = UserDefaults.standard.string(forKey: "relayURL") ?? "https://relay.org-social.org"
maxPostAgeDays = UserDefaults.standard.integer(forKey: "maxPostAgeDays").nonZero ?? 7
useRelay = UserDefaults.standard.object(forKey: "useRelay") as? Bool ?? true
showAllRelayFeeds = UserDefaults.standard.object(forKey: "showAllRelayFeeds") as? Bool ?? false
previewServiceEnabled = UserDefaults.standard.object(forKey: "previewServiceEnabled") as? Bool ?? true
previewServiceURL = UserDefaults.standard.string(forKey: "previewServiceURL") ?? "https://preview.org-social.org/"
vfileURL = UserDefaults.standard.string(forKey: "vfileURL") ?? ""
githubToken = UserDefaults.standard.string(forKey: "githubToken") ?? ""
githubOwner = UserDefaults.standard.string(forKey: "githubOwner") ?? ""
githubRepo = UserDefaults.standard.string(forKey: "githubRepo") ?? ""
githubPath = UserDefaults.standard.string(forKey: "githubPath").nilIfEmpty ?? "social.org"
githubBranch = UserDefaults.standard.string(forKey: "githubBranch").nilIfEmpty ?? "main"
codebergToken = UserDefaults.standard.string(forKey: "codebergToken") ?? ""
codebergOwner = UserDefaults.standard.string(forKey: "codebergOwner") ?? ""
codebergRepo = UserDefaults.standard.string(forKey: "codebergRepo") ?? ""
codebergPath = UserDefaults.standard.string(forKey: "codebergPath").nilIfEmpty ?? "social.org"
codebergBranch = UserDefaults.standard.string(forKey: "codebergBranch").nilIfEmpty ?? "main"
codebergInstance = UserDefaults.standard.string(forKey: "codebergInstance").nilIfEmpty ?? "https://codeberg.org"
webdavURL = UserDefaults.standard.string(forKey: "webdavURL") ?? ""
webdavUsername = UserDefaults.standard.string(forKey: "webdavUsername") ?? ""
webdavPassword = UserDefaults.standard.string(forKey: "webdavPassword") ?? ""
}
// MARK: - Validation
var publicFeedURLValid: Bool { isHTTPURL(publicFeedURL) }
var relayURLValid: Bool { isHTTPURL(relayURL) }
var uploadConfigValid: Bool {
switch uploadMethod {
case .vfile: return isHTTPURL(vfileURL)
case .github: return !githubToken.isEmpty && !githubOwner.isEmpty && !githubRepo.isEmpty
case .codeberg: return !codebergToken.isEmpty && !codebergOwner.isEmpty && !codebergRepo.isEmpty
case .webdav: return isHTTPURL(webdavURL)
}
}
/// When the preview toggle is off the URL is irrelevant (the field is
/// disabled), so we only require validity in the on state.
var previewServiceValid: Bool { !previewServiceEnabled || isHTTPURL(previewServiceURL) }
var canSave: Bool { publicFeedURLValid && relayURLValid && uploadConfigValid && previewServiceValid }
// MARK: - Derived public URL hints
var githubDerivedPublicURL: String {
guard !githubOwner.isEmpty, !githubRepo.isEmpty else { return "" }
let p = githubPath.isEmpty ? "social.org" : githubPath
let b = githubBranch.isEmpty ? "main" : githubBranch
return "https://raw.githubusercontent.com/\(githubOwner)/\(githubRepo)/\(b)/\(p)"
}
var codebergDerivedPublicURL: String {
guard !codebergOwner.isEmpty, !codebergRepo.isEmpty else { return "" }
let inst = codebergInstance.isEmpty ? "https://codeberg.org" : codebergInstance
let p = codebergPath.isEmpty ? "social.org" : codebergPath
let b = codebergBranch.isEmpty ? "main" : codebergBranch
return "\(inst)/\(codebergOwner)/\(codebergRepo)/raw/branch/\(b)/\(p)"
}
// MARK: - Save
func saveIfValid() {
guard canSave else { return }
d.set(uploadMethod.rawValue, forKey: "uploadMethod")
d.set(publicFeedURL, forKey: "publicFeedURL")
d.set(relayURL, forKey: "relayURL")
d.set(maxPostAgeDays, forKey: "maxPostAgeDays")
d.set(useRelay, forKey: "useRelay")
d.set(showAllRelayFeeds, forKey: "showAllRelayFeeds")
d.set(previewServiceEnabled, forKey: "previewServiceEnabled")
d.set(previewServiceURL, forKey: "previewServiceURL")
d.set(vfileURL, forKey: "vfileURL")
d.set(githubToken, forKey: "githubToken")
d.set(githubOwner, forKey: "githubOwner")
d.set(githubRepo, forKey: "githubRepo")
d.set(githubPath, forKey: "githubPath")
d.set(githubBranch, forKey: "githubBranch")
d.set(codebergToken, forKey: "codebergToken")
d.set(codebergOwner, forKey: "codebergOwner")
d.set(codebergRepo, forKey: "codebergRepo")
d.set(codebergPath, forKey: "codebergPath")
d.set(codebergBranch, forKey: "codebergBranch")
d.set(codebergInstance, forKey: "codebergInstance")
d.set(webdavURL, forKey: "webdavURL")
d.set(webdavUsername, forKey: "webdavUsername")
d.set(webdavPassword, forKey: "webdavPassword")
}
private func isHTTPURL(_ raw: String) -> Bool {
guard let url = URL(string: raw) else { return false }
return url.scheme?.hasPrefix("http") == true && url.host != nil
}
}
private extension Int {
var nonZero: Int? { self == 0 ? nil : self }
}
private extension String? {
var nilIfEmpty: String? { self?.isEmpty == false ? self : nil }
}