Build iOS App / Build .ipa and publish source (push) Successful in 31s
Sideloaded apps can't use APNs (no push entitlement without Apple signing), so deliver notifications via an iOS BGAppRefreshTask that polls the monitored repos every ~15 min and fires local notifications (no Apple push needed): - new PRs that appear in the open set - CI status changes on existing PRs Persistence: NotificationState tracks announced PR ids + last-known CI status so we only fire on actual changes, not every poll. AppDelegate registers the background task and reschedules the next one. Info.plist gets UIBackgroundModes fetch + the BGTask identifier. Settings gains a Notifications toggle; also poll after a foreground inbox load so state seeds and changes are caught promptly. Bump to 3.6.
76 lines
2.9 KiB
Swift
76 lines
2.9 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
|
|
enum MergeGateMode: String, CaseIterable, Identifiable {
|
|
case warn = "Warn on red (allow merge with confirm)"
|
|
case block = "Block merge when CI is red"
|
|
|
|
var id: String { rawValue }
|
|
}
|
|
|
|
/// Central settings: instance + token (Keychain) and app preferences.
|
|
@MainActor
|
|
final class SettingsStore: ObservableObject {
|
|
static let shared = SettingsStore()
|
|
|
|
@Published var instanceURL: String {
|
|
didSet { KeychainHelper.set(instanceURL, key: "instanceURL") }
|
|
}
|
|
@Published var token: String {
|
|
didSet { KeychainHelper.set(token, key: "token") }
|
|
}
|
|
@Published var mergeGateMode: MergeGateMode {
|
|
didSet { UserDefaults.standard.set(mergeGateMode.rawValue,
|
|
forKey: "mergeGateMode") }
|
|
}
|
|
@Published var deleteBranchAfterMerge: Bool {
|
|
didSet { UserDefaults.standard.set(deleteBranchAfterMerge,
|
|
forKey: "deleteBranchAfterMerge") }
|
|
}
|
|
/// Repos to monitor for the inbox. Empty = monitor all accessible repos.
|
|
/// Stored as full names (e.g. "luis/config-nixos").
|
|
@Published var monitoredRepos: [String] {
|
|
didSet { UserDefaults.standard.set(monitoredRepos,
|
|
forKey: "monitoredRepos") }
|
|
}
|
|
/// Set when a PR is merged, so the inbox knows to reload on next appearance
|
|
/// (otherwise a just-merged PR could linger as stale in the list).
|
|
@Published var inboxNeedsRefresh = false
|
|
/// Whether background polling for push-equivalent notifications is on.
|
|
@Published var notificationsEnabled: Bool {
|
|
didSet { UserDefaults.standard.set(notificationsEnabled,
|
|
forKey: "notificationsEnabled") }
|
|
}
|
|
|
|
private init() {
|
|
instanceURL = KeychainHelper.get("instanceURL") ?? "https://gitea.bueso.eu"
|
|
token = KeychainHelper.get("token") ?? ""
|
|
let raw = UserDefaults.standard.string(forKey: "mergeGateMode")
|
|
mergeGateMode = MergeGateMode(rawValue: raw ?? "") ?? .warn
|
|
deleteBranchAfterMerge = UserDefaults.standard.bool(forKey: "deleteBranchAfterMerge")
|
|
monitoredRepos = UserDefaults.standard.stringArray(forKey: "monitoredRepos") ?? []
|
|
notificationsEnabled = UserDefaults.standard.object(forKey: "notificationsEnabled") as? Bool ?? false
|
|
}
|
|
|
|
var isConfigured: Bool {
|
|
!token.isEmpty && !instanceURL.isEmpty
|
|
}
|
|
|
|
var baseURL: URL? {
|
|
URL(string: instanceURL.hasSuffix("/") ?
|
|
String(instanceURL.dropLast()) : instanceURL)
|
|
}
|
|
|
|
func client() -> GiteaClient? {
|
|
guard let base = baseURL else { return nil }
|
|
return GiteaClient(instanceURL: base, token: token)
|
|
}
|
|
|
|
func logout() {
|
|
KeychainHelper.delete("instanceURL")
|
|
KeychainHelper.delete("token")
|
|
instanceURL = "https://gitea.bueso.eu"
|
|
token = ""
|
|
}
|
|
}
|