Add configurable iPhone widgets

This commit is contained in:
Georg Bauer
2026-08-03 22:06:32 +02:00
parent 2534010d1d
commit c67d274a95
23 changed files with 1151 additions and 56 deletions

View File

@@ -1,14 +1,19 @@
import UIKit
import WidgetKit
@MainActor
final class AppContext {
let core = GotchaCore()
let core: GotchaCore
private let window: UIWindow
private(set) var tabs = UITabBarController()
private(set) var navigationControllers: [UINavigationController] = []
init(window: UIWindow) {
self.window = window
let storageDirectory = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.de.rfc1437.gotcha"
)?.path
core = GotchaCore(storageDirectory: storageDirectory)
applyAppearance()
}
@@ -61,6 +66,21 @@ final class AppContext {
for (index, root) in replacements {
navigationControllers[index].setViewControllers([root], animated: false)
}
WidgetCenter.shared.reloadAllTimelines()
}
@discardableResult
func route(widgetURL: URL) -> Bool {
guard widgetURL.scheme == "gotcha" else { return false }
let index: Int
switch widgetURL.host {
case "home": index = 0
case "pulls": index = 3
default: return false
}
tabs.selectedIndex = index
navigationControllers[index].popToRootViewController(animated: false)
return true
}
func didAddServer(index: UInt32) throws {

View File

@@ -1,8 +1,10 @@
import UIKit
import WidgetKit
@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
private var context: AppContext?
func application(
_ application: UIApplication,
@@ -10,10 +12,26 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
let context = AppContext(window: window)
self.context = context
window.rootViewController = context.makeRootController()
window.makeKeyAndVisible()
self.window = window
context.showStartupErrorIfNeeded()
if let url = launchOptions?[.url] as? URL {
context.route(widgetURL: url)
}
return true
}
func application(
_ application: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
context?.route(widgetURL: url) ?? false
}
func applicationDidBecomeActive(_ application: UIApplication) {
WidgetCenter.shared.reloadAllTimelines()
}
}

View File

@@ -0,0 +1,52 @@
import AppIntents
import Foundation
enum WidgetEnvironment {
static let appGroup = "group.de.rfc1437.gotcha"
static func core() -> GotchaCore {
GotchaCore(
storageDirectory: FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: appGroup
)?.path
)
}
static func servers() -> [ServerRow] {
core().servers()
}
static func selection(for server: ServerRow) -> String {
"\(server.name) · \(server.url)"
}
static func server(for selection: String) -> ServerRow? {
servers().first { self.selection(for: $0) == selection }
}
}
struct WidgetServerOptionsProvider: DynamicOptionsProvider {
func results() async throws -> [String] {
WidgetEnvironment.servers().map(WidgetEnvironment.selection)
}
func defaultResult() async -> String? {
WidgetEnvironment.servers().first.map(WidgetEnvironment.selection)
}
}
struct ActivityWidgetIntent: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Activity Server"
static let description = IntentDescription("Choose the server whose recent activity appears.")
@Parameter(title: "Server", optionsProvider: WidgetServerOptionsProvider())
var server: String?
}
struct PullsWidgetIntent: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Pull Request Server"
static let description = IntentDescription("Choose the server whose open pull requests appear.")
@Parameter(title: "Server", optionsProvider: WidgetServerOptionsProvider())
var server: String?
}