Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
acd0064776
commit
da7dcc1633
@ -205,6 +205,10 @@
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
"es-US",
|
||||
es,
|
||||
fr,
|
||||
"fr-CA",
|
||||
);
|
||||
mainGroup = EAD890AE2EF1E9CE006DBA80;
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
@ -344,6 +348,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
@ -401,6 +406,7 @@
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@ -101,7 +101,7 @@ If SwiftData is configured to use CloudKit:
|
||||
static let small: CGFloat = 8
|
||||
static let medium: CGFloat = 12
|
||||
}
|
||||
enum FontSize {
|
||||
enum BaseFontSize {
|
||||
static let body: CGFloat = 14
|
||||
static let title: CGFloat = 24
|
||||
}
|
||||
@ -125,7 +125,37 @@ If SwiftData is configured to use CloudKit:
|
||||
}
|
||||
```
|
||||
- Reference design constants in views: `Design.Spacing.medium`, `Design.CornerRadius.large`, `Color.Primary.accent`.
|
||||
- Keep design constants organized by category: Spacing, CornerRadius, FontSize, IconSize, Size, Animation, Opacity, LineWidth, Shadow.
|
||||
- Keep design constants organized by category: Spacing, CornerRadius, BaseFontSize, IconSize, Size, Animation, Opacity, LineWidth, Shadow.
|
||||
|
||||
|
||||
## Dynamic Type instructions
|
||||
|
||||
- Always support Dynamic Type for accessibility; never use fixed font sizes without scaling.
|
||||
- Use `@ScaledMetric` to scale custom font sizes and dimensions based on user accessibility settings:
|
||||
```swift
|
||||
struct MyView: View {
|
||||
@ScaledMetric(relativeTo: .body) private var bodyFontSize: CGFloat = 14
|
||||
@ScaledMetric(relativeTo: .title) private var titleFontSize: CGFloat = 24
|
||||
@ScaledMetric(relativeTo: .caption) private var chipTextSize: CGFloat = 11
|
||||
|
||||
var body: some View {
|
||||
Text("Hello")
|
||||
.font(.system(size: bodyFontSize, weight: .medium))
|
||||
}
|
||||
}
|
||||
```
|
||||
- Choose the appropriate `relativeTo` text style based on the semantic purpose:
|
||||
- `.largeTitle`, `.title`, `.title2`, `.title3` for headings
|
||||
- `.headline`, `.subheadline` for emphasized content
|
||||
- `.body` for main content
|
||||
- `.callout`, `.footnote`, `.caption`, `.caption2` for smaller text
|
||||
- For constrained UI elements (chips, cards, badges) where overflow would break the design, you may use fixed sizes but document the reason:
|
||||
```swift
|
||||
// Fixed size: chip face has strict space constraints
|
||||
private let chipValueFontSize: CGFloat = 11
|
||||
```
|
||||
- Prefer system text styles when possible: `.font(.body)`, `.font(.title)`, `.font(.caption)`.
|
||||
- Test with accessibility settings: Settings > Accessibility > Display & Text Size > Larger Text.
|
||||
|
||||
|
||||
## Project structure
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -34,9 +34,11 @@ enum Design {
|
||||
static let xxxLarge: CGFloat = 28
|
||||
}
|
||||
|
||||
// MARK: - Font Sizes
|
||||
// MARK: - Base Font Sizes
|
||||
// These are base values for use with @ScaledMetric in views.
|
||||
// They will scale automatically based on user accessibility settings.
|
||||
|
||||
enum FontSize {
|
||||
enum BaseFontSize {
|
||||
static let xxSmall: CGFloat = 7
|
||||
static let xSmall: CGFloat = 9
|
||||
static let small: CGFloat = 10
|
||||
|
||||
@ -148,13 +148,16 @@ struct GameOverView: View {
|
||||
|
||||
@State private var showContent = false
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .largeTitle) private var iconSize: CGFloat = Design.BaseFontSize.display
|
||||
@ScaledMetric(relativeTo: .largeTitle) private var titleFontSize: CGFloat = Design.BaseFontSize.largeTitle
|
||||
@ScaledMetric(relativeTo: .body) private var messageFontSize: CGFloat = Design.BaseFontSize.xLarge
|
||||
@ScaledMetric(relativeTo: .body) private var statsFontSize: CGFloat = 17
|
||||
@ScaledMetric(relativeTo: .headline) private var buttonFontSize: CGFloat = Design.BaseFontSize.xLarge
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
private let iconSize = Design.FontSize.display
|
||||
private let titleFontSize = Design.FontSize.largeTitle
|
||||
private let messageFontSize = Design.FontSize.xLarge
|
||||
private let statsFontSize: CGFloat = 17
|
||||
private let buttonFontSize = Design.FontSize.xLarge
|
||||
private let modalCornerRadius = Design.CornerRadius.xxxLarge
|
||||
private let statsCornerRadius = Design.CornerRadius.large
|
||||
private let cardPadding = Design.Spacing.xxxLarge
|
||||
@ -287,6 +290,10 @@ struct CardsDisplayArea: View {
|
||||
let bankerIsWinner: Bool
|
||||
let isTie: Bool
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = 14
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 32) {
|
||||
// Player side
|
||||
@ -294,14 +301,14 @@ struct CardsDisplayArea: View {
|
||||
// Label with value
|
||||
HStack(spacing: 8) {
|
||||
Text("PLAYER")
|
||||
.font(.system(size: 14, weight: .bold, design: .rounded))
|
||||
.font(.system(size: labelFontSize, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
if !playerCards.isEmpty && playerCardsFaceUp.contains(true) {
|
||||
ValueBadge(value: playerValue, color: .blue)
|
||||
}
|
||||
}
|
||||
.frame(height: 30)
|
||||
.frame(minHeight: 30)
|
||||
|
||||
// Cards
|
||||
CompactHandView(
|
||||
@ -316,14 +323,14 @@ struct CardsDisplayArea: View {
|
||||
// Label with value
|
||||
HStack(spacing: 8) {
|
||||
Text("BANKER")
|
||||
.font(.system(size: 14, weight: .bold, design: .rounded))
|
||||
.font(.system(size: labelFontSize, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
if !bankerCards.isEmpty && bankerCardsFaceUp.contains(true) {
|
||||
ValueBadge(value: bankerValue, color: .red)
|
||||
}
|
||||
}
|
||||
.frame(height: 30)
|
||||
.frame(minHeight: 30)
|
||||
|
||||
// Cards
|
||||
CompactHandView(
|
||||
@ -350,12 +357,21 @@ struct CompactHandView: View {
|
||||
let cardsFaceUp: [Bool]
|
||||
let isWinner: Bool
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .caption) private var winBadgeFontSize: CGFloat = 10
|
||||
|
||||
// MARK: - Layout Constants
|
||||
// Fixed size: cards have strict visual constraints
|
||||
|
||||
private let cardWidth: CGFloat = 45
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: -12) {
|
||||
if cards.isEmpty {
|
||||
// Placeholders
|
||||
ForEach(0..<2, id: \.self) { _ in
|
||||
CardPlaceholderView(width: 45)
|
||||
CardPlaceholderView(width: cardWidth)
|
||||
}
|
||||
} else {
|
||||
ForEach(cards.indices, id: \.self) { index in
|
||||
@ -363,7 +379,7 @@ struct CompactHandView: View {
|
||||
CardView(
|
||||
card: cards[index],
|
||||
isFaceUp: isFaceUp,
|
||||
cardWidth: 45
|
||||
cardWidth: cardWidth
|
||||
)
|
||||
.zIndex(Double(index))
|
||||
}
|
||||
@ -380,7 +396,7 @@ struct CompactHandView: View {
|
||||
.overlay(alignment: .bottom) {
|
||||
if isWinner {
|
||||
Text("WIN")
|
||||
.font(.system(size: 10, weight: .black))
|
||||
.font(.system(size: winBadgeFontSize, weight: .black))
|
||||
.foregroundStyle(.black)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 2)
|
||||
@ -399,11 +415,16 @@ struct ValueBadge: View {
|
||||
let value: Int
|
||||
let color: Color
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var valueFontSize: CGFloat = 15
|
||||
@ScaledMetric(relativeTo: .headline) private var badgeSize: CGFloat = 26
|
||||
|
||||
var body: some View {
|
||||
Text("\(value)")
|
||||
.font(.system(size: 15, weight: .black, design: .rounded))
|
||||
.font(.system(size: valueFontSize, weight: .black, design: .rounded))
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 26, height: 26)
|
||||
.frame(width: badgeSize, height: badgeSize)
|
||||
.background(
|
||||
Circle()
|
||||
.fill(color)
|
||||
@ -461,22 +482,30 @@ struct TopBarView: View {
|
||||
let onReset: () -> Void
|
||||
let onSettings: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .caption2) private var labelFontSize: CGFloat = 9
|
||||
@ScaledMetric(relativeTo: .body) private var currencyFontSize: CGFloat = 14
|
||||
@ScaledMetric(relativeTo: .title3) private var balanceFontSize: CGFloat = 20
|
||||
@ScaledMetric(relativeTo: .caption) private var smallFontSize: CGFloat = 12
|
||||
@ScaledMetric(relativeTo: .body) private var buttonFontSize: CGFloat = 16
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
// Balance display
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("BALANCE")
|
||||
.font(.system(size: 9, weight: .medium, design: .rounded))
|
||||
.font(.system(size: labelFontSize, weight: .medium, design: .rounded))
|
||||
.foregroundStyle(.white.opacity(0.6))
|
||||
.tracking(1)
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Text("$")
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.font(.system(size: currencyFontSize, weight: .bold))
|
||||
.foregroundStyle(.yellow.opacity(0.8))
|
||||
|
||||
Text(balance, format: .number)
|
||||
.font(.system(size: 20, weight: .black, design: .rounded))
|
||||
.font(.system(size: balanceFontSize, weight: .black, design: .rounded))
|
||||
.foregroundStyle(.white)
|
||||
.contentTransition(.numericText())
|
||||
.animation(.spring(duration: 0.3), value: balance)
|
||||
@ -495,9 +524,9 @@ struct TopBarView: View {
|
||||
if showCardsRemaining {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "rectangle.portrait.on.rectangle.portrait.fill")
|
||||
.font(.system(size: 12))
|
||||
.font(.system(size: smallFontSize))
|
||||
Text("\(cardsRemaining)")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.font(.system(size: smallFontSize, weight: .medium))
|
||||
}
|
||||
.foregroundStyle(.white.opacity(0.5))
|
||||
|
||||
@ -507,7 +536,7 @@ struct TopBarView: View {
|
||||
// Settings button
|
||||
Button("Settings", systemImage: "gearshape.fill", action: onSettings)
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.system(size: 16))
|
||||
.font(.system(size: buttonFontSize))
|
||||
.foregroundStyle(.white.opacity(0.6))
|
||||
.padding(8)
|
||||
.background(
|
||||
@ -517,7 +546,7 @@ struct TopBarView: View {
|
||||
|
||||
// Reset button
|
||||
Button("Reset", systemImage: "arrow.counterclockwise", action: onReset)
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.font(.system(size: smallFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(0.6))
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
@ -538,25 +567,31 @@ struct ActionButtonsView: View {
|
||||
let onClear: () -> Void
|
||||
let onNewRound: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .body) private var clearButtonFontSize: CGFloat = 14
|
||||
@ScaledMetric(relativeTo: .headline) private var primaryButtonFontSize: CGFloat = 16
|
||||
@ScaledMetric(relativeTo: .body) private var statusFontSize: CGFloat = 14
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
if gameState.currentPhase == .betting {
|
||||
// Clear bets button
|
||||
Button("Clear", systemImage: "xmark.circle", action: onClear)
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.font(.system(size: clearButtonFontSize, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 12)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(Color(red: 0.6, green: 0.2, blue: 0.2))
|
||||
.fill(Color.Button.destructive)
|
||||
)
|
||||
.opacity(gameState.currentBets.isEmpty ? 0.5 : 1.0)
|
||||
.opacity(gameState.currentBets.isEmpty ? Design.Opacity.disabled : 1.0)
|
||||
.disabled(gameState.currentBets.isEmpty)
|
||||
|
||||
// Deal button
|
||||
Button("Deal", systemImage: "play.fill", action: onDeal)
|
||||
.font(.system(size: 16, weight: .bold))
|
||||
.font(.system(size: primaryButtonFontSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.padding(.horizontal, 32)
|
||||
.padding(.vertical, 12)
|
||||
@ -564,22 +599,19 @@ struct ActionButtonsView: View {
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 1.0, green: 0.85, blue: 0.3),
|
||||
Color(red: 0.9, green: 0.7, blue: 0.2)
|
||||
],
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
)
|
||||
.shadow(color: .yellow.opacity(0.3), radius: 6)
|
||||
.opacity(gameState.canDeal ? 1.0 : 0.5)
|
||||
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
||||
.opacity(gameState.canDeal ? 1.0 : Design.Opacity.disabled)
|
||||
.disabled(!gameState.canDeal)
|
||||
} else if gameState.currentPhase == .roundComplete {
|
||||
// New round button
|
||||
Button("New Round", systemImage: "arrow.right.circle", action: onNewRound)
|
||||
.font(.system(size: 16, weight: .bold))
|
||||
.font(.system(size: primaryButtonFontSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.padding(.horizontal, 32)
|
||||
.padding(.vertical, 12)
|
||||
@ -587,16 +619,13 @@ struct ActionButtonsView: View {
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 1.0, green: 0.85, blue: 0.3),
|
||||
Color(red: 0.9, green: 0.7, blue: 0.2)
|
||||
],
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
)
|
||||
.shadow(color: .yellow.opacity(0.3), radius: 6)
|
||||
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
||||
} else {
|
||||
// Playing indicator
|
||||
HStack(spacing: 6) {
|
||||
@ -604,8 +633,8 @@ struct ActionButtonsView: View {
|
||||
.tint(.white)
|
||||
.scaleEffect(0.8)
|
||||
Text("Dealing...")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(0.8))
|
||||
.font(.system(size: statusFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.heavy))
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 12)
|
||||
|
||||
@ -12,9 +12,12 @@ struct MiniBaccaratTableView: View {
|
||||
@Bindable var gameState: GameState
|
||||
let selectedChip: ChipDenomination
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .caption) private var tableLimitsFontSize: CGFloat = Design.BaseFontSize.small
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
private let tableLimitsFontSize = Design.FontSize.small
|
||||
private let tieZoneHeight: CGFloat = 55
|
||||
private let mainZoneHeight: CGFloat = 60
|
||||
private let tieHorizontalPadding: CGFloat = 50
|
||||
@ -198,11 +201,14 @@ struct TieBettingZone: View {
|
||||
var isAtMax: Bool = false
|
||||
let action: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var titleFontSize: CGFloat = Design.BaseFontSize.medium
|
||||
@ScaledMetric(relativeTo: .caption2) private var subtitleFontSize: CGFloat = Design.BaseFontSize.xSmall
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
private let cornerRadius = Design.CornerRadius.small
|
||||
private let titleFontSize = Design.FontSize.medium
|
||||
private let subtitleFontSize = Design.FontSize.xSmall
|
||||
private let chipTrailingPadding = Design.Spacing.small
|
||||
|
||||
// MARK: - Computed Properties
|
||||
@ -264,11 +270,14 @@ struct BankerBettingZone: View {
|
||||
var isAtMax: Bool = false
|
||||
let action: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var titleFontSize: CGFloat = Design.BaseFontSize.large
|
||||
@ScaledMetric(relativeTo: .caption2) private var subtitleFontSize: CGFloat = Design.BaseFontSize.xSmall
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
private let cornerRadius = Design.CornerRadius.medium
|
||||
private let titleFontSize = Design.FontSize.large
|
||||
private let subtitleFontSize = Design.FontSize.xSmall
|
||||
private let chipTrailingPadding = Design.Spacing.medium
|
||||
private let selectionShadowRadius = Design.Shadow.radiusSmall
|
||||
|
||||
@ -346,11 +355,14 @@ struct PlayerBettingZone: View {
|
||||
var isAtMax: Bool = false
|
||||
let action: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var titleFontSize: CGFloat = Design.BaseFontSize.large
|
||||
@ScaledMetric(relativeTo: .caption2) private var subtitleFontSize: CGFloat = Design.BaseFontSize.xSmall
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
private let cornerRadius = Design.CornerRadius.medium
|
||||
private let titleFontSize = Design.FontSize.large
|
||||
private let subtitleFontSize = Design.FontSize.xSmall
|
||||
private let chipTrailingPadding = Design.Spacing.medium
|
||||
private let selectionShadowRadius = Design.Shadow.radiusSmall
|
||||
|
||||
@ -426,11 +438,12 @@ struct ChipOnTable: View {
|
||||
var showMax: Bool = false
|
||||
|
||||
// MARK: - Layout Constants
|
||||
// Fixed sizes: chip face has strict space constraints
|
||||
|
||||
private let chipSize = Design.Size.chipSmall
|
||||
private let innerRingSize: CGFloat = 26
|
||||
private let gradientEndRadius: CGFloat = 20
|
||||
private let maxBadgeFontSize = Design.FontSize.xxSmall
|
||||
private let maxBadgeFontSize = Design.BaseFontSize.xxSmall
|
||||
private let maxBadgeOffsetX: CGFloat = 6
|
||||
private let maxBadgeOffsetY: CGFloat = -4
|
||||
|
||||
@ -451,7 +464,7 @@ struct ChipOnTable: View {
|
||||
}
|
||||
|
||||
private var textFontSize: CGFloat {
|
||||
amount >= 1000 ? Design.FontSize.small : 11
|
||||
amount >= 1000 ? Design.BaseFontSize.small : 11
|
||||
}
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
Loading…
Reference in New Issue
Block a user