Files
Gotcha/ios/Sources/AppContext.swift
2026-08-15 14:21:04 +02:00

220 lines
7.1 KiB
Swift

import UIKit
import WidgetKit
@MainActor
final class AppContext {
let core: GotchaCore
lazy var notifications = NotificationCoordinator(context: self)
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()
}
func makeRootController() -> UIViewController {
let roots: [UIViewController] = [
HomeViewController(context: self),
repositoryRoot(mode: .issues),
repositoryRoot(mode: .commits),
PullsViewController(context: self),
repositoryRoot(mode: .milestones),
]
let items = [
("Home", "house", "house.fill"),
("Issues", "exclamationmark.circle", "exclamationmark.circle.fill"),
("Repos", "books.vertical", "books.vertical.fill"),
("PRs", "arrow.triangle.pull", "arrow.triangle.pull"),
("Milestones", "flag", "flag.fill"),
]
navigationControllers = zip(roots, items).map { root, item in
let navigation = UINavigationController(rootViewController: root)
navigation.tabBarItem = UITabBarItem(
title: item.0,
image: UIImage(systemName: item.1),
selectedImage: UIImage(systemName: item.2)
)
return navigation
}
tabs.viewControllers = navigationControllers
return tabs
}
func showStartupErrorIfNeeded() {
guard let message = core.startupError() else { return }
tabs.present(errorAlert(message), animated: true)
}
func selectServer(index: UInt32) throws {
try core.selectServer(index: index)
reloadAfterServerChange()
}
func reloadAfterServerChange() {
let replacements: [(Int, UIViewController)] = [
(0, HomeViewController(context: self)),
(1, repositoryRoot(mode: .issues)),
(2, repositoryRoot(mode: .commits)),
(3, PullsViewController(context: self)),
(4, repositoryRoot(mode: .milestones)),
]
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 {
try selectServer(index: index)
}
func applyAppearance() {
switch core.settings().appearance {
case 1: window.overrideUserInterfaceStyle = .light
case 2: window.overrideUserInterfaceStyle = .dark
default: window.overrideUserInterfaceStyle = .unspecified
}
}
func route(_ activity: ActivityRow) {
route(
serverId: nil,
target: activity.target,
owner: activity.owner,
repository: activity.repository,
number: activity.number,
sha: activity.sha
)
}
func route(_ notification: NotificationRow) {
route(
serverId: notification.serverId,
target: notification.target,
owner: notification.owner,
repository: notification.repository,
number: notification.number,
sha: notification.sha
)
}
func route(notificationUserInfo userInfo: [AnyHashable: Any]) {
let target: ActivityTargetKind
switch userInfo["target"] as? String {
case "repository": target = .repository
case "issue": target = .issue
case "pull": target = .pullRequest
case "commit": target = .commit
default: target = .none
}
route(
serverId: userInfo["serverId"] as? String,
target: target,
owner: userInfo["owner"] as? String ?? "",
repository: userInfo["repository"] as? String ?? "",
number: (userInfo["number"] as? NSNumber)?.int64Value ?? 0,
sha: userInfo["sha"] as? String ?? ""
)
}
private func route(
serverId: String?,
target: ActivityTargetKind,
owner: String,
repository: String,
number: Int64,
sha: String
) {
if let serverId {
guard let index = core.servers().firstIndex(where: { $0.id == serverId }) else {
tabs.present(errorAlert("That notification's server is no longer configured."), animated: true)
return
}
if core.activeServerIndex() != UInt32(index) {
do {
try selectServer(index: UInt32(index))
} catch {
tabs.present(errorAlert(error.localizedDescription), animated: true)
return
}
}
}
let navigation = navigationControllers[0]
tabs.selectedIndex = 0
switch target {
case .repository:
navigation.pushViewController(
IssuesViewController(
context: self,
owner: owner,
repository: repository
),
animated: true
)
case .issue:
navigation.pushViewController(
IssueViewController(
context: self,
owner: owner,
repository: repository,
number: number
),
animated: true
)
case .pullRequest:
navigation.pushViewController(
PullViewController(
context: self,
owner: owner,
repository: repository,
number: number
),
animated: true
)
case .commit:
navigation.pushViewController(
FilesViewController(
context: self,
owner: owner,
repository: repository,
sha: sha
),
animated: true
)
case .none:
break
}
}
func symbol(_ name: String) -> UIImage? {
UIImage(systemName: name)
}
private func repositoryRoot(mode: RepositoryPane) -> UIViewController {
if core.activeServerIndex() == nil {
return ServersViewController(context: self)
}
return RepositoriesViewController(context: self, mode: mode)
}
}