Overhaul native approval UX and add widget surfaces
Some checks failed
CI / test (push) Has been cancelled

Bring the SwiftUI app in line with the Apple-native mock and keep pending approvals actionable from Live Activities and watch complications.
This commit is contained in:
2026-04-19 16:29:13 +02:00
parent a6939453f8
commit 61a0cc1f7d
63 changed files with 3496 additions and 1769 deletions

View File

@@ -0,0 +1,57 @@
import SwiftUI
struct PrimaryActionStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headline)
.frame(maxWidth: .infinity)
.padding(.horizontal, 12)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous)
.fill(IdP.tint)
)
.foregroundStyle(.white)
.opacity(configuration.isPressed ? 0.92 : 1)
}
}
struct SecondaryActionStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headline)
.frame(maxWidth: .infinity)
.padding(.horizontal, 12)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous)
.fill(Color.idpSecondaryGroupedBackground)
)
.overlay(
RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous)
.stroke(Color.idpSeparator, lineWidth: 1)
)
.foregroundStyle(.white)
.opacity(configuration.isPressed ? 0.92 : 1)
}
}
struct DestructiveStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headline)
.frame(maxWidth: .infinity)
.padding(.horizontal, 12)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous)
.fill(Color.red.opacity(0.18))
)
.overlay(
RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous)
.stroke(Color.red.opacity(0.25), lineWidth: 1)
)
.foregroundStyle(.red)
.opacity(configuration.isPressed ? 0.92 : 1)
}
}

View File

@@ -0,0 +1,65 @@
import SwiftUI
struct ApprovalCardModifier: ViewModifier {
var highlighted = false
func body(content: Content) -> some View {
content
.padding(14)
.background(
RoundedRectangle(cornerRadius: IdP.cardRadius, style: .continuous)
.fill(Color.idpSecondaryGroupedBackground)
)
.overlay(
RoundedRectangle(cornerRadius: IdP.cardRadius, style: .continuous)
.stroke(highlighted ? IdP.tint.opacity(0.75) : Color.idpSeparator, lineWidth: highlighted ? 1.5 : 1)
)
}
}
extension View {
func approvalCard(highlighted: Bool = false) -> some View {
modifier(ApprovalCardModifier(highlighted: highlighted))
}
}
struct RequestHeroCard: View {
let request: ApprovalRequest
let handle: String
var body: some View {
HStack(spacing: 12) {
MonogramAvatar(title: request.source, size: 40)
VStack(alignment: .leading, spacing: 4) {
Text(request.source)
.font(.headline)
.foregroundStyle(.white)
Text(handle)
.font(.footnote)
.foregroundStyle(IdP.tint)
}
}
.approvalCard(highlighted: true)
}
}
struct MonogramAvatar: View {
let title: String
var size: CGFloat = 22
private var monogram: String {
String(title.trimmingCharacters(in: .whitespacesAndNewlines).first ?? "I").uppercased()
}
var body: some View {
RoundedRectangle(cornerRadius: size * 0.34, style: .continuous)
.fill(IdP.tint.opacity(0.2))
.frame(width: size, height: size)
.overlay {
Text(monogram)
.font(.system(size: size * 0.48, weight: .semibold, design: .rounded))
.foregroundStyle(IdP.tint)
}
}
}

View File

@@ -0,0 +1,8 @@
import SwiftUI
public extension View {
@ViewBuilder
func idpGlassChrome() -> some View {
self.background(.thinMaterial)
}
}

View File

@@ -0,0 +1,16 @@
import SwiftUI
import WatchKit
enum Haptics {
static func success() {
WKInterfaceDevice.current().play(.success)
}
static func warning() {
WKInterfaceDevice.current().play(.failure)
}
static func selection() {
WKInterfaceDevice.current().play(.click)
}
}

View File

@@ -0,0 +1,15 @@
import SwiftUI
public enum IdP {
public static let tint = Color("IdPTint")
public static let cardRadius: CGFloat = 20
public static let controlRadius: CGFloat = 14
public static let badgeRadius: CGFloat = 8
}
extension Color {
static var idpGroupedBackground: Color { .black }
static var idpSecondaryGroupedBackground: Color { Color.white.opacity(0.08) }
static var idpTertiaryFill: Color { Color.white.opacity(0.12) }
static var idpSeparator: Color { Color.white.opacity(0.14) }
}

View File

@@ -0,0 +1,11 @@
import SwiftUI
struct StatusDot: View {
let color: Color
var body: some View {
Circle()
.fill(color)
.frame(width: 8, height: 8)
}
}