diff --git a/app/src/main/java/com/aldo/apps/ochecompanion/GameActivity.java b/app/src/main/java/com/aldo/apps/ochecompanion/GameActivity.java index ff59e88..d8b0841 100644 --- a/app/src/main/java/com/aldo/apps/ochecompanion/GameActivity.java +++ b/app/src/main/java/com/aldo/apps/ochecompanion/GameActivity.java @@ -9,7 +9,6 @@ import android.os.Build; import android.os.Bundle; import android.os.VibrationEffect; import android.os.Vibrator; -import android.util.Log; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; @@ -19,7 +18,6 @@ import android.widget.GridLayout; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; -import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; @@ -27,18 +25,21 @@ import androidx.core.view.WindowInsetsCompat; import androidx.preference.PreferenceManager; import com.aldo.apps.ochecompanion.database.DatabaseHelper; +import com.aldo.apps.ochecompanion.database.objects.Match; import com.aldo.apps.ochecompanion.database.objects.Player; import com.aldo.apps.ochecompanion.database.objects.Statistics; import com.aldo.apps.ochecompanion.ui.PlayerStatsView; import com.aldo.apps.ochecompanion.utils.CheckoutEngine; import com.aldo.apps.ochecompanion.utils.DartsConstants; +import com.aldo.apps.ochecompanion.utils.Log; +import com.aldo.apps.ochecompanion.utils.MatchProgress; import com.aldo.apps.ochecompanion.utils.SoundEngine; import com.aldo.apps.ochecompanion.utils.UIConstants; +import com.aldo.apps.ochecompanion.utils.converters.MatchProgressConverter; import com.google.android.material.button.MaterialButton; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.UUID; import java.util.concurrent.TimeUnit; import nl.dionsegijn.konfetti.core.PartyFactory; @@ -55,11 +56,6 @@ import nl.dionsegijn.konfetti.xml.KonfettiView; public class GameActivity extends BaseActivity { private static final String TAG = "GameActivity"; - - /** - * Intent extra key for player list. Type: ArrayList - */ - private static final String EXTRA_PLAYERS = "extra_players"; /** * Intent extra key for starting score. Type: int (typically 501, 301, or 701) @@ -69,7 +65,7 @@ public class GameActivity extends BaseActivity { /** * Intent extra for a match ID. Making it possible to load a match from the database. */ - private static final String EXTRA_MATCH_UUID = "extra_match_uuid"; + private static final String EXTRA_MATCH_ID = "extra_match_uuid"; // ======================================================================================== @@ -77,9 +73,9 @@ public class GameActivity extends BaseActivity { // ======================================================================================== /** - * The matches UUID from the database. + * The matches ID from the database. */ - private String mMatchUuid; + private int mMatchId; /** * Index of the current active player (0 to playerCount-1). @@ -129,6 +125,12 @@ public class GameActivity extends BaseActivity { */ private boolean mIsBustedTurn = false; + /** + * Flag indicating the match has been completed (a player has won). + * When true, pressing back will return to main menu instead of restarting. + */ + private boolean mIsMatchCompleted = false; + /** * Helper class to track dart hit details for statistics. */ @@ -266,14 +268,13 @@ public class GameActivity extends BaseActivity { * Starts GameActivity with specified players and starting score. * * @param context The context from which to start the activity - * @param players The list of players (can be null/empty) * @param startScore The starting score (typically 501, 301, or 701) + * @param matchId The ID of the match to be started/loaded. */ - public static void start(final Context context, final ArrayList players, final int startScore) { + public static void start(final Context context, final int startScore, final int matchId) { Intent intent = new Intent(context, GameActivity.class); - intent.putParcelableArrayListExtra(EXTRA_PLAYERS, players); intent.putExtra(EXTRA_START_SCORE, startScore); - intent.putExtra(EXTRA_MATCH_UUID, UUID.randomUUID().toString()); + intent.putExtra(EXTRA_MATCH_ID, matchId); context.startActivity(intent); } @@ -298,7 +299,7 @@ public class GameActivity extends BaseActivity { // Extract game parameters from intent mStartingScore = getIntent().getIntExtra(EXTRA_START_SCORE, DartsConstants.DEFAULT_GAME_SCORE); - mMatchUuid = getIntent().getStringExtra(EXTRA_MATCH_UUID); + mMatchId = getIntent().getIntExtra(EXTRA_MATCH_ID, -1); // Initialize activity components in order initViews(); @@ -306,13 +307,69 @@ public class GameActivity extends BaseActivity { // Only setup a new game if we're not restoring from saved state if (savedInstanceState == null) { + // New game - need to load players and create/load match new Thread(() -> { final List allAvailablePlayers = (List) mDatabaseHelper.getAllPlayers(); Log.d(TAG, "onCreate: allAvailablePlayers = [" + allAvailablePlayers + "]"); - runOnUiThread(() -> setupGame(allAvailablePlayers)); + + // Handle match loading/creation + Match match = null; + if (mMatchId > 0) { + // Try to load existing match + match = mDatabaseHelper.getMatchById(mMatchId); + Log.d(TAG, "onCreate: Loaded match from DB: " + (match != null ? "ID=" + match.id : "null")); + + if (match != null && match.participantData != null && !match.participantData.isEmpty()) { + // Load match progress from database + try { + final MatchProgress progress = MatchProgressConverter.fromString(match.participantData); + if (progress != null) { + Log.d(TAG, "onCreate: Found saved progress with " + progress.players.size() + " players"); + // Initialize player states first + runOnUiThread(() -> { + mPlayerStates = new ArrayList<>(); + if (allAvailablePlayers != null && !allAvailablePlayers.isEmpty()) { + for (Player p : allAvailablePlayers) { + mPlayerStates.add(new X01State(p, mStartingScore)); + } + } else { + final Player guest = new Player("GUEST", null); + mPlayerStates.add(new X01State(guest, mStartingScore)); + } + loadMatchProgress(progress); + }); + Log.d(TAG, "onCreate: Loaded existing match with ID: " + mMatchId); + } else { + Log.w(TAG, "onCreate: Progress is null, treating as new match"); + match = null; // Treat as new match + } + } catch (Exception e) { + Log.e(TAG, "onCreate: Failed to load match progress", e); + match = null; // Treat as new match on error + } + } else if (match != null) { + // Match exists but has no progress data yet (newly created) + Log.d(TAG, "onCreate: Match has no progress data, setting up new game"); + match = null; // Treat as new match + } + } + + if (match == null) { + // Create new match if not found or invalid ID + final long newMatchId = mDatabaseHelper.createNewMatch(String.valueOf(mStartingScore), allAvailablePlayers); + if (newMatchId > 0) { + mMatchId = (int) newMatchId; + Log.d(TAG, "onCreate: Created new match with ID: " + mMatchId); + } else { + Log.e(TAG, "onCreate: Failed to create new match"); + } + + // Setup new game + runOnUiThread(() -> setupGame(allAvailablePlayers)); + } }).start(); } else { - // We're restoring - load players synchronously since onRestoreInstanceState is called immediately + // We're restoring from configuration change - load players synchronously since onRestoreInstanceState is called immediately Log.d(TAG, "onCreate: Loading players for state restoration"); final List allAvailablePlayers = (List) mDatabaseHelper.getAllPlayers(); Log.d(TAG, "onCreate: restoring with players = [" + allAvailablePlayers + "]"); @@ -340,7 +397,18 @@ public class GameActivity extends BaseActivity { } @Override - protected void onSaveInstanceState(Bundle outState) { + public void onBackPressed() { + if (mIsMatchCompleted) { + // Match is finished, return to main menu + finish(); + } else { + // Match is ongoing, use default back button behavior (exit with confirmation if needed) + super.onBackPressed(); + } + } + + @Override + protected void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); Log.d(TAG, "onSaveInstanceState: Saving game state"); @@ -348,7 +416,7 @@ public class GameActivity extends BaseActivity { outState.putInt("activePlayerIndex", mActivePlayerIndex); outState.putInt("multiplier", mMultiplier); outState.putInt("startingScore", mStartingScore); - outState.putString("matchUuid", mMatchUuid); + outState.putInt("matchId", mMatchId); outState.putBoolean("isTurnOver", mIsTurnOver); outState.putBoolean("isBustedTurn", mIsBustedTurn); @@ -394,7 +462,7 @@ public class GameActivity extends BaseActivity { } @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { + protected void onRestoreInstanceState(final Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); Log.d(TAG, "onRestoreInstanceState: Restoring game state"); @@ -402,7 +470,7 @@ public class GameActivity extends BaseActivity { mActivePlayerIndex = savedInstanceState.getInt("activePlayerIndex", 0); mMultiplier = savedInstanceState.getInt("multiplier", 1); mStartingScore = savedInstanceState.getInt("startingScore", DartsConstants.DEFAULT_GAME_SCORE); - mMatchUuid = savedInstanceState.getString("matchUuid"); + mMatchId = savedInstanceState.getInt("matchId"); mIsTurnOver = savedInstanceState.getBoolean("isTurnOver", false); mIsBustedTurn = savedInstanceState.getBoolean("isBustedTurn", false); @@ -703,14 +771,16 @@ public class GameActivity extends BaseActivity { */ private void updatePlayerStats(final GameActivity.X01State active, final int dartsThrown, final int pointsMade, final boolean wasBust, final int checkoutValue) { if (active.player != null && active.player.id != 0) { - mDatabaseHelper.updatePlayerStatistics( - active.player.id, - dartsThrown, - pointsMade, - wasBust, - checkoutValue, - active.dartsThrown - ); + new Thread(() -> { + mDatabaseHelper.updatePlayerStatistics( + active.player.id, + dartsThrown, + pointsMade, + wasBust, + checkoutValue, + active.dartsThrown + ); + }).start(); } } @@ -779,6 +849,8 @@ public class GameActivity extends BaseActivity { mIsTurnOver = false; mIsBustedTurn = false; + saveMatchProgress(); + // Update UI for next player updateUI(); updateTurnIndicators(); @@ -789,8 +861,70 @@ public class GameActivity extends BaseActivity { * Saves the current game state to the database. */ private void saveMatchProgress() { - // TODO: Persist current state to Room using mMatchUuid - // This allows the "Continue Game" feature on the Main Menu + final MatchProgress progress = new MatchProgress(); + progress.activePlayerIndex = mActivePlayerIndex; + progress.startingScore = mStartingScore; + progress.players = new ArrayList<>(); + + for (X01State state : mPlayerStates) { + progress.players.add(new MatchProgress.PlayerStateSnapshot( + state.player.id, + state.name, + state.remainingScore, + state.dartsThrown + )); + } + + String progressJson = MatchProgressConverter.fromProgress(progress); + + if (mMatchId > 0) { + new Thread(() -> { + try { + // Load existing match and update its progress + Match match = mDatabaseHelper.getMatchById(mMatchId); + if (match != null) { + match.participantData = progressJson; + match.timestamp = System.currentTimeMillis(); + mDatabaseHelper.updateMatch(match); + Log.d(TAG, "saveMatchProgress: Saved match progress for match ID: " + mMatchId); + } else { + Log.e(TAG, "saveMatchProgress: Match not found with ID: " + mMatchId); + } + } catch (Exception e) { + Log.e(TAG, "saveMatchProgress: Failed to save progress", e); + } + }).start(); + } + } + + /** + * Loads match progress from a saved state. + * Updates all player scores and game state to match the saved progress. + * + * @param progress The MatchProgress to load + */ + private void loadMatchProgress(final MatchProgress progress) { + if (progress == null || mPlayerStates == null) return; + + Log.d(TAG, "loadMatchProgress: Loading saved match progress"); + + // Restore active player index + mActivePlayerIndex = progress.activePlayerIndex; + + // Restore player scores and darts thrown + for (int i = 0; i < progress.players.size() && i < mPlayerStates.size(); i++) { + MatchProgress.PlayerStateSnapshot snapshot = progress.players.get(i); + X01State state = mPlayerStates.get(i); + state.remainingScore = snapshot.remainingScore; + state.dartsThrown = snapshot.dartsThrown; + } + + // Update UI to reflect loaded state + updateUI(); + updateTurnIndicators(); + setMultiplier(DartsConstants.MULTIPLIER_SINGLE); + + Log.d(TAG, "loadMatchProgress: Match progress loaded successfully"); } /** @@ -925,7 +1059,9 @@ public class GameActivity extends BaseActivity { * @param isMissed true if the double-out was missed, false if successful */ private void trackDoubleAttempt(final X01State playerState, final boolean isMissed) { - mDatabaseHelper.trackDoubleAttempt(playerState.playerId, isMissed); + new Thread(() -> { + mDatabaseHelper.trackDoubleAttempt(playerState.playerId, isMissed); + }).start(); } /** @@ -938,7 +1074,9 @@ public class GameActivity extends BaseActivity { for (X01State playerState : mPlayerStates) { playerIds.add(playerState.playerId); } - mDatabaseHelper.incrementMatchesPlayed(playerIds); + new Thread(() -> { + mDatabaseHelper.incrementMatchesPlayed(playerIds); + }).start(); } /** @@ -958,8 +1096,10 @@ public class GameActivity extends BaseActivity { for (DartHit hit : dartHits) { dbDartHits.add(new DatabaseHelper.DartHit(hit.baseValue, hit.multiplier)); } - - mDatabaseHelper.recordDartHits(playerState.playerId, dbDartHits); + + new Thread(() -> { + mDatabaseHelper.recordDartHits(playerState.playerId, dbDartHits); + }).start(); } /** @@ -998,9 +1138,56 @@ public class GameActivity extends BaseActivity { mShowStatsBtn.setVisibility(View.VISIBLE); attachPlayerStats(); - // TODO: Consider adding: - // - Save match to database - // - Offer rematch + + // Save completed match to database + saveCompletedMatch(winner); + + // Mark match as completed so back button returns to main menu + mIsMatchCompleted = true; + } + + /** + * Saves the completed match to the database with final results. + * + * @param winner The winning player's game state + */ + private void saveCompletedMatch(final X01State winner) { + if (mMatchId <= 0) return; + + new Thread(() -> { + try { + Match match = mDatabaseHelper.getMatchById(mMatchId); + if (match != null) { + // Mark match as completed + match.state = Match.MatchState.COMPLETED; + match.timestamp = System.currentTimeMillis(); + + // Save final progress state + final MatchProgress finalProgress = new MatchProgress(); + finalProgress.activePlayerIndex = mActivePlayerIndex; + finalProgress.startingScore = mStartingScore; + finalProgress.players = new ArrayList<>(); + + for (X01State state : mPlayerStates) { + finalProgress.players.add(new MatchProgress.PlayerStateSnapshot( + state.player.id, + state.name, + state.remainingScore, + state.dartsThrown + )); + } + + match.participantData = MatchProgressConverter.fromProgress(finalProgress); + mDatabaseHelper.updateMatch(match); + + Log.d(TAG, "saveCompletedMatch: Match " + mMatchId + " marked as completed"); + } else { + Log.e(TAG, "saveCompletedMatch: Match not found with ID: " + mMatchId); + } + } catch (Exception e) { + Log.e(TAG, "saveCompletedMatch: Failed to save completed match", e); + } + }).start(); } private void attachPlayerStats() { diff --git a/app/src/main/java/com/aldo/apps/ochecompanion/utils/Log.java b/app/src/main/java/com/aldo/apps/ochecompanion/utils/Log.java new file mode 100644 index 0000000..6511b71 --- /dev/null +++ b/app/src/main/java/com/aldo/apps/ochecompanion/utils/Log.java @@ -0,0 +1,4 @@ +package com.aldo.apps.ochecompanion.utils; + +public class Log { +} diff --git a/app/src/main/java/com/aldo/apps/ochecompanion/utils/MatchProgress.java b/app/src/main/java/com/aldo/apps/ochecompanion/utils/MatchProgress.java new file mode 100644 index 0000000..9c0a1e3 --- /dev/null +++ b/app/src/main/java/com/aldo/apps/ochecompanion/utils/MatchProgress.java @@ -0,0 +1,4 @@ +package com.aldo.apps.ochecompanion.utils; + +public class MatchProgress { +} diff --git a/app/src/main/java/com/aldo/apps/ochecompanion/utils/converters/MatchProgressConverter.java b/app/src/main/java/com/aldo/apps/ochecompanion/utils/converters/MatchProgressConverter.java new file mode 100644 index 0000000..b3d8ca4 --- /dev/null +++ b/app/src/main/java/com/aldo/apps/ochecompanion/utils/converters/MatchProgressConverter.java @@ -0,0 +1,4 @@ +package com.aldo.apps.ochecompanion.utils.converters; + +public class MatchProgressConverter { +}