1.0.0 for iOS

This commit is contained in:
2025-12-07 23:58:13 -07:00
parent ff6009f237
commit 766f72dcfa
10 changed files with 66 additions and 17 deletions

View File

@@ -425,7 +425,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atri.dad.MagicCounter.MagicCounter; PRODUCT_BUNDLE_IDENTIFIER = com.atri.dad.MagicCounter.MagicCounter;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -459,7 +459,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atri.dad.MagicCounter.MagicCounter; PRODUCT_BUNDLE_IDENTIFIER = com.atri.dad.MagicCounter.MagicCounter;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;

View File

@@ -112,3 +112,20 @@ struct SettingSlider: View {
} }
} }
} }
/**
* Helper for haptic feedback.
*/
enum Haptics {
static func play(_ style: UIImpactFeedbackGenerator.FeedbackStyle) {
guard UserDefaults.standard.bool(forKey: "hapticFeedbackEnabled") else { return }
let generator = UIImpactFeedbackGenerator(style: style)
generator.impactOccurred()
}
static func notification(_ type: UINotificationFeedbackGenerator.FeedbackType) {
guard UserDefaults.standard.bool(forKey: "hapticFeedbackEnabled") else { return }
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(type)
}
}

View File

@@ -63,7 +63,7 @@ struct ContentView: View {
} }
} }
.listStyle(.insetGrouped) .listStyle(.insetGrouped)
.navigationTitle("History") .navigationTitle("Games")
.toolbar { .toolbar {
ToolbarItem(placement: .primaryAction) { ToolbarItem(placement: .primaryAction) {
Button(action: { showSetup = true }) { Button(action: { showSetup = true }) {
@@ -73,7 +73,7 @@ struct ContentView: View {
} }
} }
.tabItem { .tabItem {
Label("History", systemImage: "clock.fill") Label("Games", systemImage: "clock.fill")
} }
SettingsView() SettingsView()

View File

@@ -76,7 +76,6 @@ struct GameView: View {
.clipShape(Circle()) .clipShape(Circle())
} }
} else { } else {
// Placeholder for balance
Color.clear.frame(width: 40, height: 40) Color.clear.frame(width: 40, height: 40)
} }
} }
@@ -85,9 +84,6 @@ struct GameView: View {
// Players Grid // Players Grid
GeometryReader { geometry in GeometryReader { geometry in
// Use adaptive grid with minimum width to handle responsiveness
// 320pt minimum ensures 1 column on iPhone Portrait (width ~390)
// and 2 columns on iPhone Landscape (width ~844) or iPad
let columns = [GridItem(.adaptive(minimum: 320), spacing: 24)] let columns = [GridItem(.adaptive(minimum: 320), spacing: 24)]
ScrollView { ScrollView {
LazyVGrid(columns: columns, spacing: 24) { LazyVGrid(columns: columns, spacing: 24) {
@@ -128,6 +124,9 @@ struct GameView: View {
.presentationDetents([.medium]) .presentationDetents([.medium])
} }
.onChange(of: gameState) { newState in .onChange(of: gameState) { newState in
if newState.winner != nil {
Haptics.notification(.success)
}
gameManager.updateActiveGame(state: newState) gameManager.updateActiveGame(state: newState)
} }
} }
@@ -172,6 +171,8 @@ struct PlayerCell: View {
let onCommanderTap: () -> Void let onCommanderTap: () -> Void
let onScoop: () -> Void let onScoop: () -> Void
@State private var showScoopConfirmation = false
var body: some View { var body: some View {
ZStack { ZStack {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 24)
@@ -192,12 +193,8 @@ struct PlayerCell: View {
.foregroundStyle(.primary) .foregroundStyle(.primary)
Spacer() Spacer()
Menu { Button(action: { showScoopConfirmation = true }) {
Button(role: .destructive, action: onScoop) { Image(systemName: "flag.fill")
Label("Scoop", systemImage: "flag.fill")
}
} label: {
Image(systemName: "ellipsis.circle")
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.padding(4) .padding(4)
} }
@@ -257,20 +254,39 @@ struct PlayerCell: View {
.opacity(player.isEliminated && !isWinner ? 0.8 : 1) .opacity(player.isEliminated && !isWinner ? 0.8 : 1)
.scaleEffect(isWinner ? 1.05 : 1) .scaleEffect(isWinner ? 1.05 : 1)
.animation(.spring, value: isWinner) .animation(.spring, value: isWinner)
.alert("Scoop?", isPresented: $showScoopConfirmation) {
Button("Cancel", role: .cancel) { }
Button("Scoop", role: .destructive) {
Haptics.notification(.warning)
onScoop()
}
} message: {
Text("Are you sure you want to scoop?")
}
} }
private func adjustLife(by amount: Int) { private func adjustLife(by amount: Int) {
if player.isEliminated { return } if player.isEliminated { return }
Haptics.play(.light)
var newPlayer = player var newPlayer = player
newPlayer.life += amount newPlayer.life += amount
onUpdate(newPlayer) onUpdate(newPlayer)
if newPlayer.isEliminated && !player.isEliminated {
Haptics.notification(.error)
}
} }
private func adjustPoison(by amount: Int) { private func adjustPoison(by amount: Int) {
if player.isEliminated { return } if player.isEliminated { return }
Haptics.play(.light)
var newPlayer = player var newPlayer = player
newPlayer.poison = max(0, newPlayer.poison + amount) newPlayer.poison = max(0, newPlayer.poison + amount)
onUpdate(newPlayer) onUpdate(newPlayer)
if newPlayer.isEliminated && !player.isEliminated {
Haptics.notification(.error)
}
} }
} }
@@ -328,11 +344,16 @@ struct CommanderDamageView: View {
} }
private func adjustCommanderDamage(attackerId: Int, by amount: Int) { private func adjustCommanderDamage(attackerId: Int, by amount: Int) {
Haptics.play(.light)
var newPlayer = targetPlayer var newPlayer = targetPlayer
var damages = newPlayer.commanderDamages var damages = newPlayer.commanderDamages
let current = damages[attackerId] ?? 0 let current = damages[attackerId] ?? 0
damages[attackerId] = max(0, current + amount) damages[attackerId] = max(0, current + amount)
newPlayer.commanderDamages = damages newPlayer.commanderDamages = damages
onUpdate(newPlayer) onUpdate(newPlayer)
if newPlayer.isEliminated && !targetPlayer.isEliminated {
Haptics.notification(.error)
}
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -7,10 +7,10 @@
{ {
"layers" : [ "layers" : [
{ {
"image-name" : "MagicCounter 2.png", "image-name" : "logo 2.png",
"name" : "MagicCounter 2", "name" : "logo 2",
"position" : { "position" : {
"scale" : 0.8, "scale" : 0.85,
"translation-in-points" : [ "translation-in-points" : [
0, 0,
0 0

View File

@@ -10,6 +10,12 @@ import SwiftUI
@main @main
struct MagicCounterApp: App { struct MagicCounterApp: App {
@StateObject private var gameManager = GameManager() @StateObject private var gameManager = GameManager()
init() {
UserDefaults.standard.register(defaults: [
"hapticFeedbackEnabled": true
])
}
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {

View File

@@ -9,6 +9,7 @@ import SwiftUI
struct SettingsView: View { struct SettingsView: View {
@AppStorage("accentColorName") private var accentColorName = "Blue" @AppStorage("accentColorName") private var accentColorName = "Blue"
@AppStorage("hapticFeedbackEnabled") private var hapticFeedbackEnabled = true
private let colors: [(name: String, color: Color)] = [ private let colors: [(name: String, color: Color)] = [
("Blue", .blue), ("Blue", .blue),
@@ -44,6 +45,10 @@ struct SettingsView: View {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
Form { Form {
Section("General") {
Toggle("Haptic Feedback", isOn: $hapticFeedbackEnabled)
}
Section("Appearance") { Section("Appearance") {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
Text("ACCENT COLOR") Text("ACCENT COLOR")