RatingBar (Nochmal/Schwer/Gut/Leicht), CardRenderer (Flip-Zustand via accessibilityValue), StudySessionView (Karte/Offline-Banner, dekorative Icons versteckt). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
3.5 KiB
Swift
95 lines
3.5 KiB
Swift
import SwiftUI
|
|
|
|
#if canImport(UIKit)
|
|
import UIKit
|
|
#endif
|
|
|
|
/// Vier Rating-Buttons mit emphasis auf "Good" (full-width primary).
|
|
/// Web-Vorbild: `cards/apps/web/src/routes/study/[deckId]/+page.svelte`
|
|
/// — `.grade.again/.hard/.good/.easy`-Klassen.
|
|
struct RatingBar: View {
|
|
let onRate: (Rating) -> Void
|
|
|
|
var body: some View {
|
|
HStack(spacing: 8) {
|
|
ForEach(Rating.allCases, id: \.self) { rating in
|
|
Button {
|
|
triggerHaptic(for: rating)
|
|
onRate(rating)
|
|
} label: {
|
|
HStack(spacing: 6) {
|
|
Text(rating.label)
|
|
.font(.subheadline.weight(.semibold))
|
|
Text(rating.shortcut)
|
|
.font(.caption2.weight(.semibold))
|
|
.padding(.horizontal, 5)
|
|
.padding(.vertical, 1)
|
|
.background(kbdBackground(for: rating), in: RoundedRectangle(cornerRadius: 4))
|
|
.foregroundStyle(kbdForeground(for: rating))
|
|
.accessibilityHidden(true)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 14)
|
|
.background(background(for: rating), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
|
|
.foregroundStyle(foreground(for: rating))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
.stroke(borderColor(for: rating), lineWidth: rating == .good ? 0 : 1)
|
|
)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.accessibilityLabel(rating.label)
|
|
}
|
|
}
|
|
.padding(.horizontal, 16)
|
|
}
|
|
|
|
/// `good` ist die Hero-Action (primary full background) — analog
|
|
/// zum Web-Default-Klick. Andere bekommen subtle tinted borders.
|
|
private func background(for rating: Rating) -> Color {
|
|
switch rating {
|
|
case .again: WordeckTheme.error.opacity(0.06)
|
|
case .hard: WordeckTheme.warning.opacity(0.06)
|
|
case .good: WordeckTheme.primary
|
|
case .easy: WordeckTheme.success.opacity(0.06)
|
|
}
|
|
}
|
|
|
|
private func foreground(for rating: Rating) -> Color {
|
|
switch rating {
|
|
case .again: WordeckTheme.error
|
|
case .hard: WordeckTheme.warning
|
|
case .good: WordeckTheme.primaryForeground
|
|
case .easy: WordeckTheme.success
|
|
}
|
|
}
|
|
|
|
private func borderColor(for rating: Rating) -> Color {
|
|
switch rating {
|
|
case .again: WordeckTheme.error.opacity(0.4)
|
|
case .hard: WordeckTheme.warning.opacity(0.4)
|
|
case .good: .clear
|
|
case .easy: WordeckTheme.success.opacity(0.4)
|
|
}
|
|
}
|
|
|
|
private func kbdBackground(for rating: Rating) -> Color {
|
|
rating == .good
|
|
? WordeckTheme.primaryForeground.opacity(0.18)
|
|
: WordeckTheme.muted
|
|
}
|
|
|
|
private func kbdForeground(for rating: Rating) -> Color {
|
|
rating == .good
|
|
? WordeckTheme.primaryForeground.opacity(0.85)
|
|
: WordeckTheme.mutedForeground
|
|
}
|
|
|
|
private func triggerHaptic(for rating: Rating) {
|
|
#if canImport(UIKit)
|
|
let style: UIImpactFeedbackGenerator.FeedbackStyle =
|
|
rating == .easy ? .heavy : .medium
|
|
UIImpactFeedbackGenerator(style: style).impactOccurred()
|
|
#endif
|
|
}
|
|
}
|