Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
4d79f08089
commit
c358d3b2ae
@ -3480,28 +3480,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Hint: %@" : {
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Hint: %@"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es-MX" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Consejo: %@"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fr-CA" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Conseil: %@"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Hit" : {
|
"Hit" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
|
|||||||
@ -62,6 +62,7 @@ enum Design {
|
|||||||
static let hintIconSize: CGFloat = 24
|
static let hintIconSize: CGFloat = 24
|
||||||
static let hintPaddingH: CGFloat = 10
|
static let hintPaddingH: CGFloat = 10
|
||||||
static let hintPaddingV: CGFloat = 10
|
static let hintPaddingV: CGFloat = 10
|
||||||
|
static let hintMinWidth: CGFloat = 90
|
||||||
|
|
||||||
// Hand icons
|
// Hand icons
|
||||||
static let handIconSize: CGFloat = 18
|
static let handIconSize: CGFloat = 18
|
||||||
|
|||||||
@ -42,29 +42,28 @@ struct GameTableView: View {
|
|||||||
return .infinity
|
return .infinity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Provides the current game state, creating one if needed (fallback for initial render).
|
||||||
|
private var state: GameState {
|
||||||
|
gameState ?? GameState(settings: settings)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
mainGameView(state: state)
|
||||||
if let state = gameState {
|
.onAppear {
|
||||||
mainGameView(state: state)
|
if gameState == nil {
|
||||||
} else {
|
gameState = GameState(settings: settings)
|
||||||
ProgressView()
|
}
|
||||||
.task {
|
|
||||||
gameState = GameState(settings: settings)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
.sheet(isPresented: $showSettings) {
|
||||||
.sheet(isPresented: $showSettings) {
|
|
||||||
SettingsView(settings: settings, gameState: gameState)
|
SettingsView(settings: settings, gameState: gameState)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showRules) {
|
.sheet(isPresented: $showRules) {
|
||||||
RulesHelpView()
|
RulesHelpView()
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showStats) {
|
.sheet(isPresented: $showStats) {
|
||||||
if let state = gameState {
|
StatisticsSheetView(state: state)
|
||||||
StatisticsSheetView(state: state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,29 @@ struct BlackjackTableView: View {
|
|||||||
// Use global debug flag from Design constants
|
// Use global debug flag from Design constants
|
||||||
private var showDebugBorders: Bool { Design.showDebugBorders }
|
private var showDebugBorders: Bool { Design.showDebugBorders }
|
||||||
|
|
||||||
|
// MARK: - Hint Toast Helper
|
||||||
|
|
||||||
|
/// Shows the hint toast with auto-dismiss timer.
|
||||||
|
private func showHintToastWithTimer(state: GameState) {
|
||||||
|
// Generate new ID to invalidate any pending dismiss tasks
|
||||||
|
let currentID = UUID()
|
||||||
|
state.hintDisplayID = currentID
|
||||||
|
|
||||||
|
withAnimation(.spring(duration: Design.Animation.springDuration)) {
|
||||||
|
state.showHintToast = true
|
||||||
|
}
|
||||||
|
// Auto-dismiss after delay, but only if this is still the active hint session
|
||||||
|
Task { @MainActor in
|
||||||
|
try? await Task.sleep(for: Design.Toast.duration)
|
||||||
|
// Only dismiss if no newer hint has arrived
|
||||||
|
if state.hintDisplayID == currentID {
|
||||||
|
withAnimation(.spring(duration: Design.Animation.springDuration)) {
|
||||||
|
state.showHintToast = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Dealer area
|
// Dealer area
|
||||||
@ -99,7 +122,9 @@ struct BlackjackTableView: View {
|
|||||||
isPlayerTurn: state.isPlayerTurn,
|
isPlayerTurn: state.isPlayerTurn,
|
||||||
showCardCount: showCardCount,
|
showCardCount: showCardCount,
|
||||||
cardWidth: cardWidth,
|
cardWidth: cardWidth,
|
||||||
cardSpacing: cardSpacing
|
cardSpacing: cardSpacing,
|
||||||
|
currentHint: state.currentHint,
|
||||||
|
showHintToast: state.showHintToast
|
||||||
)
|
)
|
||||||
|
|
||||||
// Side bet toasts (positioned on left/right sides to not cover cards)
|
// Side bet toasts (positioned on left/right sides to not cover cards)
|
||||||
@ -132,33 +157,10 @@ struct BlackjackTableView: View {
|
|||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay {
|
|
||||||
// Hint toast overlaying player hands (auto-dismisses)
|
|
||||||
if state.showHintToast, let hint = state.currentHint {
|
|
||||||
HintView(hint: hint)
|
|
||||||
.transition(.scale.combined(with: .opacity))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: state.currentHint) { oldHint, newHint in
|
.onChange(of: state.currentHint) { oldHint, newHint in
|
||||||
// Show toast when a new hint appears
|
// Show toast when a new hint appears
|
||||||
if let hint = newHint, hint != oldHint {
|
if let hint = newHint, hint != oldHint {
|
||||||
// Generate new ID to invalidate any pending dismiss tasks
|
showHintToastWithTimer(state: state)
|
||||||
let currentID = UUID()
|
|
||||||
state.hintDisplayID = currentID
|
|
||||||
|
|
||||||
withAnimation(.spring(duration: Design.Animation.springDuration)) {
|
|
||||||
state.showHintToast = true
|
|
||||||
}
|
|
||||||
// Auto-dismiss after delay, but only if this is still the active hint session
|
|
||||||
Task { @MainActor in
|
|
||||||
try? await Task.sleep(for: Design.Toast.duration)
|
|
||||||
// Only dismiss if no newer hint has arrived
|
|
||||||
if state.hintDisplayID == currentID {
|
|
||||||
withAnimation(.spring(duration: Design.Animation.springDuration)) {
|
|
||||||
state.showHintToast = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if newHint == nil {
|
} else if newHint == nil {
|
||||||
// Hide immediately when no hint
|
// Hide immediately when no hint
|
||||||
state.hintDisplayID = UUID() // Invalidate any pending dismiss tasks
|
state.hintDisplayID = UUID() // Invalidate any pending dismiss tasks
|
||||||
@ -167,6 +169,29 @@ struct BlackjackTableView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: state.playerHands.count) { _, _ in
|
||||||
|
// Show hint when hands are added (split occurred)
|
||||||
|
if state.currentHint != nil {
|
||||||
|
showHintToastWithTimer(state: state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: state.activeHandIndex) { _, _ in
|
||||||
|
// Show hint when active hand changes (moved to next hand after split)
|
||||||
|
if state.currentHint != nil {
|
||||||
|
showHintToastWithTimer(state: state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: state.activeHand?.cards.count) { _, newCount in
|
||||||
|
// Show hint when a card is added to the active hand (after hit)
|
||||||
|
guard let count = newCount, count > 2, state.currentHint != nil else { return }
|
||||||
|
// Small delay to let card animation settle before showing hint
|
||||||
|
Task { @MainActor in
|
||||||
|
try? await Task.sleep(for: .milliseconds(250))
|
||||||
|
if state.currentHint != nil {
|
||||||
|
showHintToastWithTimer(state: state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.padding(.bottom, 5)
|
.padding(.bottom, 5)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
.debugBorder(showDebugBorders, color: .green, label: "Player")
|
.debugBorder(showDebugBorders, color: .green, label: "Player")
|
||||||
|
|||||||
@ -24,7 +24,7 @@ struct HintView: View {
|
|||||||
Image(systemName: "lightbulb.fill")
|
Image(systemName: "lightbulb.fill")
|
||||||
.font(.system(size: iconSize))
|
.font(.system(size: iconSize))
|
||||||
.foregroundStyle(.yellow)
|
.foregroundStyle(.yellow)
|
||||||
Text(String(localized: "Hint: \(hint)"))
|
Text(hint)
|
||||||
.font(.system(size: fontSize, weight: .medium))
|
.font(.system(size: fontSize, weight: .medium))
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
@ -32,6 +32,7 @@ struct HintView: View {
|
|||||||
}
|
}
|
||||||
.padding(.horizontal, paddingH)
|
.padding(.horizontal, paddingH)
|
||||||
.padding(.vertical, paddingV)
|
.padding(.vertical, paddingV)
|
||||||
|
.frame(minWidth: Design.Size.hintMinWidth, alignment: .leading)
|
||||||
.background(
|
.background(
|
||||||
Capsule()
|
Capsule()
|
||||||
.fill(Color.black.opacity(Design.Opacity.heavy))
|
.fill(Color.black.opacity(Design.Opacity.heavy))
|
||||||
|
|||||||
@ -14,6 +14,8 @@ struct InsurancePopupView: View {
|
|||||||
let onTake: () -> Void
|
let onTake: () -> Void
|
||||||
let onDecline: () -> Void
|
let onDecline: () -> Void
|
||||||
|
|
||||||
|
@State private var showContent = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
// Dimmed background
|
// Dimmed background
|
||||||
@ -91,6 +93,13 @@ struct InsurancePopupView: View {
|
|||||||
RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge)
|
RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge)
|
||||||
.strokeBorder(Color.yellow.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin)
|
.strokeBorder(Color.yellow.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin)
|
||||||
)
|
)
|
||||||
|
.scaleEffect(showContent ? Design.Scale.normal : Design.Scale.slightShrink)
|
||||||
|
.opacity(showContent ? 1.0 : 0)
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce)) {
|
||||||
|
showContent = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.accessibilityElement(children: .contain)
|
.accessibilityElement(children: .contain)
|
||||||
.accessibilityAddTraits(.isModal)
|
.accessibilityAddTraits(.isModal)
|
||||||
|
|||||||
@ -19,6 +19,12 @@ struct PlayerHandsView: View {
|
|||||||
let cardWidth: CGFloat
|
let cardWidth: CGFloat
|
||||||
let cardSpacing: CGFloat
|
let cardSpacing: CGFloat
|
||||||
|
|
||||||
|
/// Current hint to display (shown on active hand only).
|
||||||
|
let currentHint: String?
|
||||||
|
|
||||||
|
/// Whether the hint toast should be visible.
|
||||||
|
let showHintToast: Bool
|
||||||
|
|
||||||
/// Total card count across all hands - used to trigger scroll when hitting
|
/// Total card count across all hands - used to trigger scroll when hitting
|
||||||
private var totalCardCount: Int {
|
private var totalCardCount: Int {
|
||||||
hands.reduce(0) { $0 + $1.cards.count }
|
hands.reduce(0) { $0 + $1.cards.count }
|
||||||
@ -32,14 +38,18 @@ struct PlayerHandsView: View {
|
|||||||
// Visual order: Hand 3, Hand 2, Hand 1 (left to right)
|
// Visual order: Hand 3, Hand 2, Hand 1 (left to right)
|
||||||
// Play order: Hand 1 played first (rightmost), then Hand 2, etc.
|
// Play order: Hand 1 played first (rightmost), then Hand 2, etc.
|
||||||
ForEach(Array(hands.enumerated()).reversed(), id: \.element.id) { index, hand in
|
ForEach(Array(hands.enumerated()).reversed(), id: \.element.id) { index, hand in
|
||||||
|
let isActiveHand = index == activeHandIndex && isPlayerTurn
|
||||||
PlayerHandView(
|
PlayerHandView(
|
||||||
hand: hand,
|
hand: hand,
|
||||||
isActive: index == activeHandIndex && isPlayerTurn,
|
isActive: isActiveHand,
|
||||||
showCardCount: showCardCount,
|
showCardCount: showCardCount,
|
||||||
// Hand numbers: rightmost (index 0) is Hand 1, played first
|
// Hand numbers: rightmost (index 0) is Hand 1, played first
|
||||||
handNumber: hands.count > 1 ? index + 1 : nil,
|
handNumber: hands.count > 1 ? index + 1 : nil,
|
||||||
cardWidth: cardWidth,
|
cardWidth: cardWidth,
|
||||||
cardSpacing: cardSpacing
|
cardSpacing: cardSpacing,
|
||||||
|
// Only show hint on the active hand
|
||||||
|
currentHint: isActiveHand ? currentHint : nil,
|
||||||
|
showHintToast: isActiveHand && showHintToast
|
||||||
)
|
)
|
||||||
.id(hand.id)
|
.id(hand.id)
|
||||||
.transition(.scale.combined(with: .opacity))
|
.transition(.scale.combined(with: .opacity))
|
||||||
@ -89,6 +99,12 @@ struct PlayerHandView: View {
|
|||||||
let cardWidth: CGFloat
|
let cardWidth: CGFloat
|
||||||
let cardSpacing: CGFloat
|
let cardSpacing: CGFloat
|
||||||
|
|
||||||
|
/// Current hint to display on this hand.
|
||||||
|
let currentHint: String?
|
||||||
|
|
||||||
|
/// Whether the hint toast should be visible.
|
||||||
|
let showHintToast: Bool
|
||||||
|
|
||||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
||||||
@ScaledMetric(relativeTo: .caption) private var handNumberSize: CGFloat = Design.Size.handNumberFontSize
|
@ScaledMetric(relativeTo: .caption) private var handNumberSize: CGFloat = Design.Size.handNumberFontSize
|
||||||
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.handIconSize
|
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.handIconSize
|
||||||
@ -132,7 +148,7 @@ struct PlayerHandView: View {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.overlay {
|
.overlay {
|
||||||
// Result badge - centered on cards
|
// Result badge - centered on cards (takes priority over hint)
|
||||||
if let result = hand.result {
|
if let result = hand.result {
|
||||||
Text(result.displayText)
|
Text(result.displayText)
|
||||||
.font(.system(size: labelFontSize, weight: .black))
|
.font(.system(size: labelFontSize, weight: .black))
|
||||||
@ -145,6 +161,10 @@ struct PlayerHandView: View {
|
|||||||
.shadow(color: .black.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusMedium)
|
.shadow(color: .black.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusMedium)
|
||||||
)
|
)
|
||||||
.transition(.scale.combined(with: .opacity))
|
.transition(.scale.combined(with: .opacity))
|
||||||
|
} else if showHintToast, let hint = currentHint {
|
||||||
|
// Hint toast - centered on active hand (only when no result)
|
||||||
|
HintView(hint: hint)
|
||||||
|
.transition(.scale.combined(with: .opacity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
@ -218,7 +238,9 @@ struct PlayerHandView: View {
|
|||||||
isPlayerTurn: true,
|
isPlayerTurn: true,
|
||||||
showCardCount: false,
|
showCardCount: false,
|
||||||
cardWidth: 60,
|
cardWidth: 60,
|
||||||
cardSpacing: -20
|
cardSpacing: -20,
|
||||||
|
currentHint: nil,
|
||||||
|
showHintToast: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,7 +257,9 @@ struct PlayerHandView: View {
|
|||||||
isPlayerTurn: true,
|
isPlayerTurn: true,
|
||||||
showCardCount: false,
|
showCardCount: false,
|
||||||
cardWidth: 60,
|
cardWidth: 60,
|
||||||
cardSpacing: -20
|
cardSpacing: -20,
|
||||||
|
currentHint: "Hit",
|
||||||
|
showHintToast: true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,7 +290,9 @@ struct PlayerHandView: View {
|
|||||||
isPlayerTurn: true,
|
isPlayerTurn: true,
|
||||||
showCardCount: true,
|
showCardCount: true,
|
||||||
cardWidth: 60,
|
cardWidth: 60,
|
||||||
cardSpacing: -20
|
cardSpacing: -20,
|
||||||
|
currentHint: "Stand",
|
||||||
|
showHintToast: true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ public final class CloudSyncManager<T: PersistableGameData> {
|
|||||||
public var iCloudAvailable: Bool {
|
public var iCloudAvailable: Bool {
|
||||||
let token = FileManager.default.ubiquityIdentityToken
|
let token = FileManager.default.ubiquityIdentityToken
|
||||||
let available = token != nil
|
let available = token != nil
|
||||||
print("CloudSyncManager: iCloud available = \(available), token = \(String(describing: token))")
|
CasinoDesign.debugLog("CloudSyncManager: iCloud available = \(available), token = \(String(describing: token))")
|
||||||
return available
|
return available
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,30 +120,30 @@ public final class CloudSyncManager<T: PersistableGameData> {
|
|||||||
|
|
||||||
/// Checks for iCloud data after a brief delay (for fresh installs).
|
/// Checks for iCloud data after a brief delay (for fresh installs).
|
||||||
private func scheduleDelayedCloudCheck() {
|
private func scheduleDelayedCloudCheck() {
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Scheduling delayed cloud check...")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Scheduling delayed cloud check...")
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
// Wait for iCloud to sync (typically takes 1-2 seconds on fresh install)
|
// Wait for iCloud to sync (typically takes 1-2 seconds on fresh install)
|
||||||
try? await Task.sleep(for: .seconds(2))
|
try? await Task.sleep(for: .seconds(2))
|
||||||
|
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Delayed check - forcing sync...")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Delayed check - forcing sync...")
|
||||||
|
|
||||||
// Force another sync (on main thread to avoid concurrency warning)
|
// Force another sync (on main thread to avoid concurrency warning)
|
||||||
var syncResult = false
|
var syncResult = false
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
syncResult = iCloudStore.synchronize()
|
syncResult = iCloudStore.synchronize()
|
||||||
}
|
}
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: synchronize() returned \(syncResult)")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: synchronize() returned \(syncResult)")
|
||||||
|
|
||||||
// Check what's in the store
|
// Check what's in the store
|
||||||
let allKeys = iCloudStore.dictionaryRepresentation.keys
|
let allKeys = iCloudStore.dictionaryRepresentation.keys
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: iCloud store keys: \(Array(allKeys))")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: iCloud store keys: \(Array(allKeys))")
|
||||||
|
|
||||||
// Try loading cloud data again
|
// Try loading cloud data again
|
||||||
if let cloudData = loadCloud() {
|
if let cloudData = loadCloud() {
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Found cloud data with \(cloudData.roundsPlayed) rounds")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Found cloud data with \(cloudData.roundsPlayed) rounds")
|
||||||
if cloudData.roundsPlayed > data.roundsPlayed {
|
if cloudData.roundsPlayed > data.roundsPlayed {
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Cloud has more data, updating...")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Cloud has more data, updating...")
|
||||||
data = cloudData
|
data = cloudData
|
||||||
hasCompletedInitialSync = true
|
hasCompletedInitialSync = true
|
||||||
onCloudDataReceived?(cloudData)
|
onCloudDataReceived?(cloudData)
|
||||||
@ -160,11 +160,11 @@ public final class CloudSyncManager<T: PersistableGameData> {
|
|||||||
userInfo: ["gameIdentifier": T.gameIdentifier]
|
userInfo: ["gameIdentifier": T.gameIdentifier]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Local data has same or more rounds")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Local data has same or more rounds")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hasCompletedInitialSync = true
|
hasCompletedInitialSync = true
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: No cloud data found after delay")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: No cloud data found after delay")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ public final class CloudSyncManager<T: PersistableGameData> {
|
|||||||
self.data = dataToSave
|
self.data = dataToSave
|
||||||
|
|
||||||
guard let encoded = try? encoder.encode(dataToSave) else {
|
guard let encoded = try? encoder.encode(dataToSave) else {
|
||||||
print("CloudSyncManager: Failed to encode game data")
|
CasinoDesign.debugLog("CloudSyncManager: Failed to encode game data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ public final class CloudSyncManager<T: PersistableGameData> {
|
|||||||
syncStatus = "Synced"
|
syncStatus = "Synced"
|
||||||
}
|
}
|
||||||
|
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Saved (rounds: \(dataToSave.roundsPlayed))")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Saved (rounds: \(dataToSave.roundsPlayed))")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience to update and save in one call.
|
/// Convenience to update and save in one call.
|
||||||
@ -216,31 +216,31 @@ public final class CloudSyncManager<T: PersistableGameData> {
|
|||||||
|
|
||||||
switch (localData, cloudData) {
|
switch (localData, cloudData) {
|
||||||
case (nil, nil):
|
case (nil, nil):
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: No saved data, using empty")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: No saved data, using empty")
|
||||||
finalData = T.empty
|
finalData = T.empty
|
||||||
|
|
||||||
case (let local?, nil):
|
case (let local?, nil):
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Using local data")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Using local data")
|
||||||
finalData = local
|
finalData = local
|
||||||
|
|
||||||
case (nil, let cloud?):
|
case (nil, let cloud?):
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Using iCloud data")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Using iCloud data")
|
||||||
finalData = cloud
|
finalData = cloud
|
||||||
|
|
||||||
case (let local?, let cloud?):
|
case (let local?, let cloud?):
|
||||||
// Use whichever has more rounds played
|
// Use whichever has more rounds played
|
||||||
if cloud.roundsPlayed > local.roundsPlayed {
|
if cloud.roundsPlayed > local.roundsPlayed {
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Using iCloud (more progress: \(cloud.roundsPlayed) vs \(local.roundsPlayed))")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Using iCloud (more progress: \(cloud.roundsPlayed) vs \(local.roundsPlayed))")
|
||||||
finalData = cloud
|
finalData = cloud
|
||||||
// Update local with cloud data
|
// Update local with cloud data
|
||||||
if let encoded = try? encoder.encode(cloud) {
|
if let encoded = try? encoder.encode(cloud) {
|
||||||
UserDefaults.standard.set(encoded, forKey: localKey)
|
UserDefaults.standard.set(encoded, forKey: localKey)
|
||||||
}
|
}
|
||||||
} else if local.lastModified > cloud.lastModified {
|
} else if local.lastModified > cloud.lastModified {
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Using local (newer: \(local.lastModified))")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Using local (newer: \(local.lastModified))")
|
||||||
finalData = local
|
finalData = local
|
||||||
} else {
|
} else {
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Using local data")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Using local data")
|
||||||
finalData = local
|
finalData = local
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,7 +305,7 @@ public final class CloudSyncManager<T: PersistableGameData> {
|
|||||||
switch reason {
|
switch reason {
|
||||||
case NSUbiquitousKeyValueStoreServerChange,
|
case NSUbiquitousKeyValueStoreServerChange,
|
||||||
NSUbiquitousKeyValueStoreInitialSyncChange:
|
NSUbiquitousKeyValueStoreInitialSyncChange:
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: Data changed from another device")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: Data changed from another device")
|
||||||
syncStatus = "Received update"
|
syncStatus = "Received update"
|
||||||
|
|
||||||
// Reload and notify
|
// Reload and notify
|
||||||
@ -327,11 +327,11 @@ public final class CloudSyncManager<T: PersistableGameData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case NSUbiquitousKeyValueStoreQuotaViolationChange:
|
case NSUbiquitousKeyValueStoreQuotaViolationChange:
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: iCloud quota exceeded")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: iCloud quota exceeded")
|
||||||
syncStatus = "Storage full"
|
syncStatus = "Storage full"
|
||||||
|
|
||||||
case NSUbiquitousKeyValueStoreAccountChange:
|
case NSUbiquitousKeyValueStoreAccountChange:
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: iCloud account changed")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: iCloud account changed")
|
||||||
syncStatus = "Account changed"
|
syncStatus = "Account changed"
|
||||||
// Reload with new account
|
// Reload with new account
|
||||||
data = load()
|
data = load()
|
||||||
@ -355,7 +355,7 @@ public final class CloudSyncManager<T: PersistableGameData> {
|
|||||||
|
|
||||||
data = T.empty
|
data = T.empty
|
||||||
syncStatus = "Data cleared"
|
syncStatus = "Data cleared"
|
||||||
print("CloudSyncManager[\(T.gameIdentifier)]: All data cleared")
|
CasinoDesign.debugLog("CloudSyncManager[\(T.gameIdentifier)]: All data cleared")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,20 @@ import SwiftUI
|
|||||||
/// Shared design constants for casino game components.
|
/// Shared design constants for casino game components.
|
||||||
public enum CasinoDesign {
|
public enum CasinoDesign {
|
||||||
|
|
||||||
|
// MARK: - Debug
|
||||||
|
|
||||||
|
/// Set to true to enable debug logging in CasinoKit.
|
||||||
|
public static let showDebugLogs = false
|
||||||
|
|
||||||
|
/// Logs a message only in debug builds when `showDebugLogs` is enabled.
|
||||||
|
public static func debugLog(_ message: String) {
|
||||||
|
#if DEBUG
|
||||||
|
if showDebugLogs {
|
||||||
|
print(message)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Spacing
|
// MARK: - Spacing
|
||||||
|
|
||||||
public enum Spacing {
|
public enum Spacing {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user