Add push-equivalent notifications via background polling; bump to 3.6
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.
This commit is contained in:
Hermes committed 2026-08-24 08:18:29 +00:00
1 parent 6ed50ecc4a
commit bae531f850
8 files changed
+241 -4

No files matched your search

+6
View File
@@ -36,6 +36,11 @@ final class SettingsStore: ObservableObject {
/// 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"
@@ -44,6 +49,7 @@ final class SettingsStore: ObservableObject {
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 {
+11
View File
@@ -7,6 +7,7 @@ struct SettingsView: View {
@State private var mergeGateMode: MergeGateMode
@State private var deleteBranchAfterMerge: Bool
@State private var monitoredRepos: [String]
@State private var notificationsEnabled: Bool
@State private var testResult: TestResult?
@State private var testing = false
@State private var showLogoutConfirm = false
@@ -22,6 +23,7 @@ struct SettingsView: View {
_mergeGateMode = State(initialValue: SettingsStore.shared.mergeGateMode)
_deleteBranchAfterMerge = State(initialValue: SettingsStore.shared.deleteBranchAfterMerge)
_monitoredRepos = State(initialValue: SettingsStore.shared.monitoredRepos)
_notificationsEnabled = State(initialValue: SettingsStore.shared.notificationsEnabled)
}
var body: some View {
@@ -66,6 +68,14 @@ struct SettingsView: View {
Text("Limit the landing page to specific repos to speed it up. Leave empty to monitor all accessible repos.")
}
Section {
Toggle("Notifications", isOn: $notificationsEnabled)
} header: {
Text("Notifications")
} footer: {
Text("Check periodically for new PRs and CI status changes, then send a local notification. Best-effort: iOS schedules this opportunistically (~15 min) and only when the app was used recently.")
}
Section("Diagnostics") {
NavigationLink("View logs") {
LogsView()
@@ -147,6 +157,7 @@ struct SettingsView: View {
SettingsStore.shared.mergeGateMode = mergeGateMode
SettingsStore.shared.deleteBranchAfterMerge = deleteBranchAfterMerge
SettingsStore.shared.monitoredRepos = monitoredRepos
SettingsStore.shared.notificationsEnabled = notificationsEnabled
dismiss()
}
}