Fixed the Group Standing view
The group standing view had an issue with not being able to display participant data correctly due to multiple issue and data not being available, not it is available and working properly
This commit is contained in:
@@ -23,13 +23,14 @@ import com.aldo.apps.ochecompanion.ui.MatchRecapView;
|
||||
import com.aldo.apps.ochecompanion.ui.QuickStartButton;
|
||||
import com.aldo.apps.ochecompanion.ui.adapter.MainMenuPlayerAdapter;
|
||||
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
||||
import com.aldo.apps.ochecompanion.utils.UIConstants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main entry point and home screen of the Oche Companion application.
|
||||
* Displays the squad of players, allows adding new players, and shows match recap with test data.
|
||||
* Displays the squad of players, allows adding new players, and shows match
|
||||
* recap.
|
||||
*/
|
||||
public class MainMenuActivity extends BaseActivity {
|
||||
|
||||
@@ -40,67 +41,61 @@ public class MainMenuActivity extends BaseActivity {
|
||||
|
||||
/**
|
||||
* Custom view component that displays a match summary.
|
||||
* Can be clicked to cycle through different test data states.
|
||||
*/
|
||||
private MatchRecapView mMatchRecap;
|
||||
|
||||
/**
|
||||
* Counter for cycling through different test data scenarios.
|
||||
* Increments on each click to cycle through null match, 1v1 match, and group match states.
|
||||
*/
|
||||
private int mTestCounter = 0;
|
||||
|
||||
/**
|
||||
* The {@link SharedPreferences} containing the currently selected settings.
|
||||
*/
|
||||
private SharedPreferences mSettingsPref;
|
||||
|
||||
/**
|
||||
* Centralized database helper that manages all database operations with proper synchronization.
|
||||
* Centralized database helper that manages all database operations with proper
|
||||
* synchronization.
|
||||
*/
|
||||
private DatabaseHelper mDatabaseHelper;
|
||||
|
||||
/** The ongoing match retrieved from the database, if any. Used for quick start functionality. */
|
||||
/**
|
||||
* The ongoing match retrieved from the database, if any. Used for quick start
|
||||
* functionality.
|
||||
*/
|
||||
private Match mOngoingMatch;
|
||||
|
||||
/**
|
||||
* Initializes the activity: enables edge-to-edge display, configures window insets,
|
||||
* Initializes the activity: enables edge-to-edge display, configures window
|
||||
* insets,
|
||||
* and sets up the match recap view with test data click listener.
|
||||
*
|
||||
* @param savedInstanceState Bundle containing saved state, or null if none exists.
|
||||
* @param savedInstanceState Bundle containing saved state, or null if none
|
||||
* exists.
|
||||
*/
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
|
||||
// Enable edge-to-edge display for immersive UI experience
|
||||
EdgeToEdge.enable(this);
|
||||
setContentView(R.layout.activity_main);
|
||||
mSettingsPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
mDatabaseHelper = DatabaseHelper.getInstance(this);
|
||||
|
||||
// Configure window insets to properly handle system bars (status bar, navigation bar)
|
||||
// Configure window insets to properly handle system bars (status bar,
|
||||
// navigation bar)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||
return insets;
|
||||
});
|
||||
|
||||
|
||||
findViewById(R.id.btnSettings).setOnClickListener(v -> launchSettings());
|
||||
|
||||
// Set up match recap view with test data functionality
|
||||
// Set up match recap view
|
||||
mMatchRecap = findViewById(R.id.match_recap);
|
||||
mMatchRecap.setOnClickListener(v -> {
|
||||
// Cycle through test data scenarios on each click
|
||||
applyTestData(mTestCounter);
|
||||
mTestCounter++;
|
||||
new Thread(() -> mDatabaseHelper.printAllMatches()).start();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the squad view with latest player data from the database when activity resumes.
|
||||
* Refreshes the squad view with latest player data from the database when
|
||||
* activity resumes.
|
||||
*/
|
||||
@Override
|
||||
protected void onResume() {
|
||||
@@ -132,7 +127,8 @@ public class MainMenuActivity extends BaseActivity {
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
/**game.
|
||||
/**
|
||||
* game.
|
||||
* Checks for any ongoing matches and resumes them if found,
|
||||
* otherwise starts a new game with the default score.
|
||||
*/
|
||||
@@ -159,20 +155,21 @@ public class MainMenuActivity extends BaseActivity {
|
||||
|
||||
/**
|
||||
* Initializes the squad view: sets up the RecyclerView with adapter,
|
||||
* configures the add player button, and loads all players from the database on a background thread.
|
||||
* configures the add player button, and loads all players from the database on
|
||||
* a background thread.
|
||||
*/
|
||||
private void initSquadView() {
|
||||
// Get references to UI components
|
||||
final TextView addPlayerBtn = findViewById(R.id.btnAddPlayer);
|
||||
final RecyclerView squadView = findViewById(R.id.rvSquad);
|
||||
|
||||
|
||||
// Configure RecyclerView with linear layout
|
||||
squadView.setLayoutManager(new LinearLayoutManager(MainMenuActivity.this));
|
||||
|
||||
|
||||
// Create and attach adapter
|
||||
final MainMenuPlayerAdapter adapter = new MainMenuPlayerAdapter();
|
||||
squadView.setAdapter(adapter);
|
||||
|
||||
|
||||
// Set up button to launch AddPlayerActivity
|
||||
addPlayerBtn.setOnClickListener(v -> {
|
||||
final Intent intent = new Intent(MainMenuActivity.this, AddPlayerActivity.class);
|
||||
@@ -181,73 +178,66 @@ public class MainMenuActivity extends BaseActivity {
|
||||
new Thread(() -> {
|
||||
final List<Player> allPlayers = mDatabaseHelper.getAllPlayers();
|
||||
runOnUiThread(() -> adapter.updatePlayers(allPlayers));
|
||||
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the last completed match from the database to the match recap view.
|
||||
* Enriches participant data with profile pictures from the database using batch
|
||||
* fetch.
|
||||
*/
|
||||
private void applyLastMatch() {
|
||||
new Thread(() -> {
|
||||
final List<Match.ParticipantData> participants = new ArrayList<>();
|
||||
final Match lastCompleted = mDatabaseHelper.getLastCompletedMatch();
|
||||
if (lastCompleted != null) {
|
||||
Log.d(TAG, "applyLastMatch: Applying last completed match [" + lastCompleted + "]");
|
||||
runOnUiThread(() -> mMatchRecap.setMatch(lastCompleted));
|
||||
|
||||
// Enrich participant data with profile pictures from database (batch fetch)
|
||||
participants.addAll(lastCompleted.getAllParticipants());
|
||||
|
||||
// Collect all player IDs that need enrichment
|
||||
final List<Long> playerIds = new java.util.ArrayList<>();
|
||||
for (final Match.ParticipantData participant : participants) {
|
||||
if (participant.player.id > 0) {
|
||||
playerIds.add(participant.player.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Batch fetch all players in one query
|
||||
if (!playerIds.isEmpty()) {
|
||||
Log.d(TAG, "applyLastMatch: Fetching " + playerIds.size() + " players: " + playerIds);
|
||||
final List<Player> fullPlayers = mDatabaseHelper.getPlayersByIds(playerIds);
|
||||
Log.d(TAG, "applyLastMatch: Retrieved " + fullPlayers.size() + " players from database");
|
||||
|
||||
// Create a map for quick lookup
|
||||
final java.util.Map<Long, Player> playerMap = new java.util.HashMap<>();
|
||||
for (final Player player : fullPlayers) {
|
||||
playerMap.put(player.id, player);
|
||||
Log.d(TAG, "applyLastMatch: Player " + player.id + " has profilePictureUri: "
|
||||
+ player.profilePictureUri);
|
||||
}
|
||||
|
||||
// Enrich each participant with full player data
|
||||
for (final Match.ParticipantData participant : participants) {
|
||||
final Player fullPlayer = playerMap.get(participant.player.id);
|
||||
if (fullPlayer != null) {
|
||||
Log.d(TAG, "applyLastMatch: Enriching participant " + participant.player.username
|
||||
+ " (id=" + participant.player.id + ") with profilePictureUri: "
|
||||
+ fullPlayer.profilePictureUri);
|
||||
participant.player.profilePictureUri = fullPlayer.profilePictureUri;
|
||||
participant.player.careerAverage = fullPlayer.careerAverage;
|
||||
Log.d(TAG, "applyLastMatch: After enrichment, participant.player.profilePictureUri = "
|
||||
+ participant.player.profilePictureUri);
|
||||
} else {
|
||||
// Player was deleted - participant will show with name only (no profile
|
||||
// picture)
|
||||
Log.d(TAG, "applyLastMatch: Player " + participant.player.id + " not found (deleted?)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
runOnUiThread(() -> mMatchRecap.setMatchWithParticipants(lastCompleted, participants));
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies test data to the match recap view for development and testing.
|
||||
* Cycles through null match (counter % 3 == 0), 1v1 match (counter % 3 == 1),
|
||||
* and group match (counter % 3 == 2) based on the counter value.
|
||||
*
|
||||
* @param counter Counter value used to determine which test scenario to display.
|
||||
*/
|
||||
private void applyTestData(final int counter) {
|
||||
Log.d(TAG, "applyTestData: Applying Test Data [" + counter + "]");
|
||||
// Create test player objects
|
||||
final Player playerOne = new Player(DartsConstants.TEST_PLAYER_1, null);
|
||||
playerOne.id = 1;
|
||||
final Player playerTwo = new Player(DartsConstants.TEST_PLAYER_2, null);
|
||||
playerTwo.id = 2;
|
||||
final Player playerThree = new Player(DartsConstants.TEST_PLAYER_3, null);
|
||||
playerThree.id = 3;
|
||||
final Player playerFour = new Player(DartsConstants.TEST_PLAYER_4, null);
|
||||
playerFour.id = 4;
|
||||
|
||||
// Create score maps for test matches
|
||||
final java.util.Map<Integer, Integer> scores1v1 = new java.util.HashMap<>();
|
||||
scores1v1.put(1, 0); // Player 1 won (reached 0 from 501)
|
||||
scores1v1.put(2, 157); // Player 2 remaining score
|
||||
|
||||
final java.util.Map<Integer, Integer> scoresGroup = new java.util.HashMap<>();
|
||||
scoresGroup.put(1, 0); // Player 1 won (reached 0 from 501)
|
||||
scoresGroup.put(2, 89); // Player 2 remaining score
|
||||
scoresGroup.put(3, 234); // Player 3 remaining score
|
||||
scoresGroup.put(4, 312); // Player 4 remaining score
|
||||
|
||||
// Create test match objects with different player configurations and scores
|
||||
final Match match1on1 = new Match("501", java.util.Arrays.asList(playerOne, playerTwo), scores1v1);
|
||||
match1on1.markCompleted(); // Mark as completed for test data
|
||||
|
||||
final Match matchGroup = new Match("501", java.util.Arrays.asList(playerOne, playerTwo, playerThree, playerFour), scoresGroup);
|
||||
matchGroup.markCompleted(); // Mark as completed for test data
|
||||
|
||||
// Cycle through different test scenarios based on counter value
|
||||
if (counter % UIConstants.TEST_CYCLE_MODULO == 0) {
|
||||
Log.d(TAG, "applyTestData: No recent match selected.");
|
||||
// Scenario 1: No match (null state)
|
||||
mMatchRecap.setMatch(null);
|
||||
} else if (counter % UIConstants.TEST_CYCLE_MODULO == 1) {
|
||||
Log.d(TAG, "applyTestData: 1 on 1 Match");
|
||||
// Scenario 2: 1v1 match (two players)
|
||||
mMatchRecap.setMatch(match1on1);
|
||||
} else {
|
||||
Log.d(TAG, "applyTestData: Group Match.");
|
||||
// Scenario 3: Group match (four players)
|
||||
mMatchRecap.setMatch(matchGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -629,6 +629,28 @@ public class DatabaseHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves multiple players by their IDs in a single batch query.
|
||||
* More efficient than calling getPlayerById() multiple times.
|
||||
* Blocks until the operation completes to ensure consistency with any pending
|
||||
* writes.
|
||||
*
|
||||
* @param playerIds List of player IDs to retrieve
|
||||
* @return List of players found (may be fewer than requested if some IDs don't
|
||||
* exist)
|
||||
*/
|
||||
public List<Player> getPlayersByIds(final List<Long> playerIds) {
|
||||
if (playerIds == null || playerIds.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
return mExecutor.submit(() -> mDatabase.playerDao().getPlayersByIds(playerIds)).get();
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "getPlayersByIds: Failed to submit task", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a PlayerStats entity into the database.
|
||||
*
|
||||
|
||||
@@ -76,6 +76,17 @@ public interface PlayerDao {
|
||||
@Query("SELECT * FROM players WHERE isPrimaryUser = 1 LIMIT 1")
|
||||
Player getPrimaryUser();
|
||||
|
||||
/**
|
||||
* Retrieves multiple players by their IDs in a single query.
|
||||
* Must be called on a background thread.
|
||||
*
|
||||
* @param ids List of player IDs to retrieve
|
||||
* @return List of players found (may be fewer than requested if some IDs don't
|
||||
* exist)
|
||||
*/
|
||||
@Query("SELECT * FROM players WHERE id IN (:ids)")
|
||||
List<Player> getPlayersByIds(final List<Long> ids);
|
||||
|
||||
/**
|
||||
* Clears the primary user flag from all players.
|
||||
* Used before setting a new primary user to ensure only one exists.
|
||||
|
||||
@@ -295,7 +295,8 @@ public class Match implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class representing a participant in a match with their score.
|
||||
* Data class representing a participant in a match with their score and game
|
||||
* average.
|
||||
*/
|
||||
public static class ParticipantData {
|
||||
/** The Player object */
|
||||
@@ -304,19 +305,48 @@ public class Match implements Serializable {
|
||||
/** The player's score in this match */
|
||||
public final int score;
|
||||
|
||||
public ParticipantData(final Player player, final int score) {
|
||||
/** The player's average for this specific game (points per 3 darts) */
|
||||
public final double gameAverage;
|
||||
|
||||
public ParticipantData(final Player player, final int score, final double gameAverage) {
|
||||
this.player = player;
|
||||
this.score = score;
|
||||
this.gameAverage = gameAverage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all participants with their match scores.
|
||||
* Returns all participants with their match scores and game averages.
|
||||
* Handles both legacy JSONArray format and MatchProgress JSONObject format.
|
||||
*
|
||||
* @return List of ParticipantData objects containing player info and scores
|
||||
* @return List of ParticipantData objects containing player info, scores, and
|
||||
* game averages
|
||||
*/
|
||||
public List<ParticipantData> getAllParticipants() {
|
||||
final List<ParticipantData> participants = new ArrayList<>();
|
||||
|
||||
// First, try to parse as MatchProgress format (JSONObject with "players" array)
|
||||
final MatchProgress progress = MatchProgressConverter.fromString(participantData);
|
||||
if (progress != null && progress.players != null) {
|
||||
// New format: MatchProgress object
|
||||
for (final MatchProgress.PlayerStateSnapshot playerProgress : progress.players) {
|
||||
final Player player = new Player(playerProgress.name, null);
|
||||
player.id = playerProgress.playerId;
|
||||
|
||||
final int score = playerProgress.remainingScore;
|
||||
|
||||
// Calculate game average: (scored points / darts thrown) * 3
|
||||
final int scoredPoints = progress.startingScore - playerProgress.remainingScore;
|
||||
final double gameAverage = playerProgress.dartsThrown > 0
|
||||
? (double) scoredPoints / playerProgress.dartsThrown * 3
|
||||
: 0.0;
|
||||
|
||||
participants.add(new ParticipantData(player, score, gameAverage));
|
||||
}
|
||||
return participants;
|
||||
}
|
||||
|
||||
// Fall back to legacy JSONArray format
|
||||
try {
|
||||
final JSONArray participantArray = new JSONArray(participantData);
|
||||
for (int i = 0; i < participantArray.length(); i++) {
|
||||
@@ -327,7 +357,8 @@ public class Match implements Serializable {
|
||||
player.id = participant.optInt("id", 0);
|
||||
player.careerAverage = participant.optDouble("careerAverage", 0.0);
|
||||
final int score = participant.optInt("score", 0);
|
||||
participants.add(new ParticipantData(player, score));
|
||||
// Legacy format doesn't have game average data, use 0.0
|
||||
participants.add(new ParticipantData(player, score, 0.0));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Return empty list if JSON parsing fails
|
||||
|
||||
@@ -16,14 +16,16 @@ import java.util.List;
|
||||
/**
|
||||
* GameManager: Singleton manager for handling all X01 game business logic.
|
||||
* <p>
|
||||
* This class serves as the central data pool and business logic handler for an active darts match.
|
||||
* This class serves as the central data pool and business logic handler for an
|
||||
* active darts match.
|
||||
* It manages:
|
||||
* - Match state (scores, active player, dart throws)
|
||||
* - Database operations (loading/saving match progress)
|
||||
* - Game rules (bust detection, double-out, win conditions)
|
||||
* - Statistics tracking
|
||||
* <p>
|
||||
* The GameManager decouples business logic from UI, making GameActivity a simple view controller
|
||||
* The GameManager decouples business logic from UI, making GameActivity a
|
||||
* simple view controller
|
||||
* that only handles UI updates via the GameStateCallback interface.
|
||||
*/
|
||||
public class GameManager {
|
||||
@@ -31,94 +33,94 @@ public class GameManager {
|
||||
* Tag for logging purposes.
|
||||
*/
|
||||
private static final String TAG = "GameManager";
|
||||
|
||||
|
||||
/**
|
||||
* Singleton instance of GameManager.
|
||||
* Volatile to ensure thread-safe lazy initialization.
|
||||
*/
|
||||
private static volatile GameManager sInstance;
|
||||
|
||||
|
||||
// ========================================================================================
|
||||
// Dependencies
|
||||
// ========================================================================================
|
||||
|
||||
|
||||
/**
|
||||
* Reference to the centralized database helper for all database operations.
|
||||
* Initialized once in constructor and never changed.
|
||||
*/
|
||||
private final DatabaseHelper mDatabaseHelper;
|
||||
|
||||
|
||||
/**
|
||||
* Callback interface for notifying the UI layer of game state changes.
|
||||
* Set by the UI controller (GameActivity) when it's ready to receive updates.
|
||||
*/
|
||||
private GameStateCallback mCallback;
|
||||
|
||||
|
||||
// ========================================================================================
|
||||
// Game State
|
||||
// ========================================================================================
|
||||
|
||||
|
||||
/**
|
||||
* The database ID of the current match.
|
||||
* -1 indicates no match is loaded.
|
||||
*/
|
||||
private int mMatchId = -1;
|
||||
|
||||
|
||||
/**
|
||||
* The starting score for this X01 game (typically 501, 301, or 701).
|
||||
*/
|
||||
private int mStartingScore = DartsConstants.DEFAULT_GAME_SCORE;
|
||||
|
||||
|
||||
/**
|
||||
* Index of the currently active player (0 to playerCount-1).
|
||||
* Cycles through players as turns complete.
|
||||
*/
|
||||
private int mActivePlayerIndex = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Current multiplier for the next dart (1=Single, 2=Double, 3=Triple).
|
||||
* Resets to 1 after each dart throw for safety.
|
||||
*/
|
||||
private int mMultiplier = 1;
|
||||
|
||||
|
||||
/**
|
||||
* List of all player states in this match.
|
||||
* Order determines turn order.
|
||||
* Thread-safe operations through GameManager's single-threaded nature.
|
||||
*/
|
||||
private final List<PlayerState> mPlayerStates = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Point values of darts thrown in the current turn (up to 3).
|
||||
* Cleared when turn is submitted or reset.
|
||||
*/
|
||||
private final List<Integer> mCurrentTurnDarts = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Detailed dart hit information (base value and multiplier) for current turn.
|
||||
* Parallel to mCurrentTurnDarts, used for statistics and heat map tracking.
|
||||
* Cleared when turn is submitted or reset.
|
||||
*/
|
||||
private final List<DartHit> mCurrentTurnDartHits = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Flag indicating the current turn has ended (bust, win, or 3 darts thrown).
|
||||
* Prevents additional dart entry until turn is submitted.
|
||||
*/
|
||||
private boolean mIsTurnOver = false;
|
||||
|
||||
|
||||
/**
|
||||
* Flag indicating the current turn resulted in a bust.
|
||||
* Used to prevent UI from subtracting bust darts from score display.
|
||||
*/
|
||||
private boolean mIsBustedTurn = false;
|
||||
|
||||
|
||||
/**
|
||||
* Flag indicating the match has been completed (a player has won).
|
||||
* When true, game state is frozen and match is marked as COMPLETED in database.
|
||||
*/
|
||||
private boolean mIsMatchCompleted = false;
|
||||
|
||||
|
||||
/**
|
||||
* Callback interface for communicating game state changes to the UI layer.
|
||||
*/
|
||||
@@ -127,41 +129,43 @@ public class GameManager {
|
||||
* Called when the game state has changed and UI should be refreshed.
|
||||
*/
|
||||
void onGameStateChanged();
|
||||
|
||||
|
||||
/**
|
||||
* Called when the turn indicators (dart pills) should be updated.
|
||||
*/
|
||||
void onTurnIndicatorsChanged();
|
||||
|
||||
|
||||
/**
|
||||
* Called when the multiplier has changed.
|
||||
*
|
||||
* @param multiplier The new multiplier value (1=Single, 2=Double, 3=Triple)
|
||||
*/
|
||||
void onMultiplierChanged(int multiplier);
|
||||
|
||||
|
||||
/**
|
||||
* Called when a bust occurs.
|
||||
*/
|
||||
void onBust();
|
||||
|
||||
|
||||
/**
|
||||
* Called when a player wins the match.
|
||||
* @param winner The winning player's state
|
||||
*
|
||||
* @param winner The winning player's state
|
||||
* @param checkoutValue The final dart value that won the game
|
||||
*/
|
||||
void onPlayerWin(PlayerState winner, int checkoutValue);
|
||||
|
||||
|
||||
/**
|
||||
* Called when a perfect 180 is scored.
|
||||
*/
|
||||
void onOneEightyScored();
|
||||
|
||||
|
||||
/**
|
||||
* Called to reset visual effects after a bust.
|
||||
*/
|
||||
void onResetVisuals();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents a single dart hit with its base value and multiplier.
|
||||
* Immutable data class used for tracking individual dart throws.
|
||||
@@ -171,16 +175,16 @@ public class GameManager {
|
||||
* The dartboard number hit (1-20 or 25 for bull).
|
||||
*/
|
||||
public final int baseValue;
|
||||
|
||||
|
||||
/**
|
||||
* The multiplier applied to the base value (1=single, 2=double, 3=triple).
|
||||
*/
|
||||
public final int multiplier;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a DartHit with the specified base value and multiplier.
|
||||
*
|
||||
* @param baseValue The dartboard number (1-20 or 25 for bull)
|
||||
* @param baseValue The dartboard number (1-20 or 25 for bull)
|
||||
* @param multiplier The multiplier (1=single, 2=double, 3=triple)
|
||||
*/
|
||||
public DartHit(final int baseValue, final int multiplier) {
|
||||
@@ -188,7 +192,7 @@ public class GameManager {
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* State holder for a single player's X01 game progress.
|
||||
* Tracks current match state for an individual player.
|
||||
@@ -199,35 +203,35 @@ public class GameManager {
|
||||
* Used for statistics updates and player information.
|
||||
*/
|
||||
public final Player player;
|
||||
|
||||
|
||||
/**
|
||||
* Player's database ID for quick lookups.
|
||||
* 0 for guest players who don't have database entries.
|
||||
*/
|
||||
public final long playerId;
|
||||
|
||||
|
||||
/**
|
||||
* Player's display name cached from the Player entity.
|
||||
* Stored for convenience to avoid repeated lookups.
|
||||
*/
|
||||
public final String name;
|
||||
|
||||
|
||||
/**
|
||||
* Player's current remaining score in this match.
|
||||
* Decreases with valid throws, resets to previous value on bust.
|
||||
*/
|
||||
public int remainingScore;
|
||||
|
||||
|
||||
/**
|
||||
* Total number of darts thrown by this player in the current match.
|
||||
* Used for calculating averages and statistics.
|
||||
*/
|
||||
public int dartsThrown = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a PlayerState for a player with the specified starting score.
|
||||
*
|
||||
* @param player The Player entity from the database
|
||||
* @param player The Player entity from the database
|
||||
* @param startScore The starting score for this X01 game (e.g., 501)
|
||||
*/
|
||||
public PlayerState(final Player player, final int startScore) {
|
||||
@@ -237,17 +241,18 @@ public class GameManager {
|
||||
this.remainingScore = startScore;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor to enforce singleton pattern.
|
||||
* Initializes the database helper with application context.
|
||||
*
|
||||
* @param context Application or Activity context (will be converted to application context)
|
||||
* @param context Application or Activity context (will be converted to
|
||||
* application context)
|
||||
*/
|
||||
private GameManager(final Context context) {
|
||||
mDatabaseHelper = DatabaseHelper.getInstance(context);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the singleton instance of GameManager.
|
||||
*
|
||||
@@ -260,7 +265,7 @@ public class GameManager {
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers a callback to receive game state updates.
|
||||
* Immediately triggers an initial state change callback to synchronize the UI.
|
||||
@@ -269,20 +274,24 @@ public class GameManager {
|
||||
*/
|
||||
public void setCallback(final GameStateCallback callback) {
|
||||
mCallback = callback;
|
||||
//Send one initial callback to sync UI with current state
|
||||
// Send one initial callback to sync UI with current state
|
||||
notifyGameStateChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new game or loads an existing match from the database with explicit player list.
|
||||
* This overload allows passing a pre-loaded player list instead of loading from database.
|
||||
* Initializes a new game or loads an existing match from the database with
|
||||
* explicit player list.
|
||||
* This overload allows passing a pre-loaded player list instead of loading from
|
||||
* database.
|
||||
*
|
||||
* @param matchId The match ID to load, or -1 to create a new match
|
||||
* @param matchId The match ID to load, or -1 to create a new match
|
||||
* @param startingScore The starting score (501, 301, etc.)
|
||||
* @param players Pre-loaded list of players to use for this match
|
||||
* @param onComplete Callback executed on the calling thread when initialization is complete
|
||||
* @param players Pre-loaded list of players to use for this match
|
||||
* @param onComplete Callback executed on the calling thread when
|
||||
* initialization is complete
|
||||
*/
|
||||
public void initializeMatch(final int matchId, final int startingScore, final List<Player> players, final Runnable onComplete) {
|
||||
public void initializeMatch(final int matchId, final int startingScore, final List<Player> players,
|
||||
final Runnable onComplete) {
|
||||
mStartingScore = startingScore;
|
||||
mMatchId = matchId;
|
||||
|
||||
@@ -293,13 +302,13 @@ public class GameManager {
|
||||
match = mDatabaseHelper.getMatchById(matchId);
|
||||
Log.d(TAG, "initializeMatch: Loaded match from DB: " + match);
|
||||
|
||||
|
||||
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, "initializeMatch: Found saved progress with " + progress.players.size() + " players");
|
||||
Log.d(TAG, "initializeMatch: Found saved progress with " + progress.players.size()
|
||||
+ " players");
|
||||
// Initialize player states
|
||||
initializePlayerStates(players);
|
||||
loadMatchProgress(progress);
|
||||
@@ -341,33 +350,33 @@ public class GameManager {
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes a new game or loads an existing match from the database.
|
||||
* This should be called when starting/resuming a match.
|
||||
*
|
||||
* @param matchId The match ID to load, or -1 to create a new match
|
||||
* @param matchId The match ID to load, or -1 to create a new match
|
||||
* @param startingScore The starting score (501, 301, etc.)
|
||||
* @param onComplete Callback when initialization is complete
|
||||
* @param onComplete Callback when initialization is complete
|
||||
*/
|
||||
public void initializeMatch(final int matchId, final int startingScore, final Runnable onComplete) {
|
||||
mStartingScore = startingScore;
|
||||
mMatchId = matchId;
|
||||
|
||||
|
||||
new Thread(() -> {
|
||||
final List<Player> allPlayers = mDatabaseHelper.getAllPlayers();
|
||||
Log.d(TAG, "initializeMatch: Loading players, count = " + (allPlayers != null ? allPlayers.size() : 0));
|
||||
initializeMatch(matchId, startingScore, allPlayers, onComplete);
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes player states from the provided player list.
|
||||
* Clears any existing player states and creates new PlayerState objects
|
||||
* for each player with the current starting score.
|
||||
* If no players are provided, creates a single guest player.
|
||||
*
|
||||
* @param players List of Player entities to initialize, or null/empty for guest player
|
||||
* @param players List of Player entities to initialize, or null/empty for guest
|
||||
* player
|
||||
*/
|
||||
private void initializePlayerStates(final List<Player> players) {
|
||||
mPlayerStates.clear();
|
||||
@@ -381,7 +390,7 @@ public class GameManager {
|
||||
mPlayerStates.add(new PlayerState(guest, mStartingScore));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads match progress from a saved state.
|
||||
* Restores player scores, darts thrown, and active player index from
|
||||
@@ -390,13 +399,14 @@ public class GameManager {
|
||||
* @param progress The MatchProgress snapshot to restore from
|
||||
*/
|
||||
private void loadMatchProgress(final MatchProgress progress) {
|
||||
if (progress == null || mPlayerStates.isEmpty()) return;
|
||||
|
||||
if (progress == null || mPlayerStates.isEmpty())
|
||||
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);
|
||||
@@ -404,74 +414,77 @@ public class GameManager {
|
||||
state.remainingScore = snapshot.remainingScore;
|
||||
state.dartsThrown = snapshot.dartsThrown;
|
||||
}
|
||||
|
||||
|
||||
Log.d(TAG, "loadMatchProgress: Match progress loaded successfully");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes a dart throw when a keyboard number is tapped.
|
||||
*
|
||||
* @param baseValue Face value of the number hit (1-20 or 25 for Bull)
|
||||
*/
|
||||
public void onNumberTap(final int baseValue) {
|
||||
if (mCurrentTurnDarts.size() >= 3 || mIsTurnOver) return;
|
||||
|
||||
if (mCurrentTurnDarts.size() >= 3 || mIsTurnOver)
|
||||
return;
|
||||
|
||||
int points = baseValue * mMultiplier;
|
||||
if (baseValue == DartsConstants.BULL_VALUE && mMultiplier == DartsConstants.MULTIPLIER_TRIPLE) {
|
||||
points = DartsConstants.DOUBLE_BULL_VALUE; // Triple Bull is Double Bull
|
||||
}
|
||||
|
||||
|
||||
PlayerState active = mPlayerStates.get(mActivePlayerIndex);
|
||||
int scoreBeforeDart = active.remainingScore;
|
||||
for (int d : mCurrentTurnDarts) scoreBeforeDart -= d;
|
||||
|
||||
for (int d : mCurrentTurnDarts)
|
||||
scoreBeforeDart -= d;
|
||||
|
||||
int scoreAfterDart = scoreBeforeDart - points;
|
||||
boolean isDouble = (mMultiplier == DartsConstants.MULTIPLIER_DOUBLE) || (points == DartsConstants.DOUBLE_BULL_VALUE);
|
||||
|
||||
boolean isDouble = (mMultiplier == DartsConstants.MULTIPLIER_DOUBLE)
|
||||
|| (points == DartsConstants.DOUBLE_BULL_VALUE);
|
||||
|
||||
// --- DOUBLE OUT LOGIC CHECK ---
|
||||
if (scoreAfterDart < 0 || scoreAfterDart == DartsConstants.BUST_SCORE || (scoreAfterDart == 0 && !isDouble)) {
|
||||
// BUST CONDITION
|
||||
mCurrentTurnDarts.add(points);
|
||||
mCurrentTurnDartHits.add(new DartHit(baseValue, mMultiplier));
|
||||
|
||||
|
||||
// Track double-out miss if trying to finish but failed
|
||||
if (scoreBeforeDart <= 40 && isDouble) {
|
||||
trackDoubleAttempt(active, true);
|
||||
}
|
||||
|
||||
|
||||
mIsTurnOver = true;
|
||||
mIsBustedTurn = true;
|
||||
|
||||
|
||||
notifyTurnIndicatorsChanged();
|
||||
notifyBust();
|
||||
} else if (scoreAfterDart == 0 && isDouble) {
|
||||
// VICTORY CONDITION
|
||||
mCurrentTurnDarts.add(points);
|
||||
mCurrentTurnDartHits.add(new DartHit(baseValue, mMultiplier));
|
||||
|
||||
|
||||
// Track successful double-out
|
||||
trackDoubleAttempt(active, false);
|
||||
|
||||
|
||||
mIsTurnOver = true;
|
||||
|
||||
|
||||
notifyTurnIndicatorsChanged();
|
||||
handleWin(active);
|
||||
} else {
|
||||
// VALID THROW
|
||||
mCurrentTurnDarts.add(points);
|
||||
mCurrentTurnDartHits.add(new DartHit(baseValue, mMultiplier));
|
||||
|
||||
|
||||
notifyTurnIndicatorsChanged();
|
||||
notifyGameStateChanged();
|
||||
|
||||
|
||||
if (mCurrentTurnDarts.size() == DartsConstants.MAX_DARTS_PER_TURN) {
|
||||
mIsTurnOver = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setMultiplier(DartsConstants.MULTIPLIER_SINGLE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles the win condition when a player finishes on zero with a double.
|
||||
* Updates statistics, records dart hits, increments match counters, saves the
|
||||
@@ -482,86 +495,93 @@ public class GameManager {
|
||||
private void handleWin(final PlayerState winner) {
|
||||
final int dartsThrown = mCurrentTurnDarts.size();
|
||||
int pointsMade = 0;
|
||||
for (int d : mCurrentTurnDarts) pointsMade += d;
|
||||
for (int d : mCurrentTurnDarts)
|
||||
pointsMade += d;
|
||||
final int checkoutValue = mCurrentTurnDarts.get(mCurrentTurnDarts.size() - 1);
|
||||
|
||||
|
||||
// Update winner's score to 0 and increment darts thrown
|
||||
winner.remainingScore = 0;
|
||||
winner.dartsThrown += dartsThrown;
|
||||
|
||||
// Update statistics
|
||||
updatePlayerStats(winner, dartsThrown, pointsMade, false, checkoutValue);
|
||||
|
||||
|
||||
// Record dart hits
|
||||
recordTurnHitsToStatistics(winner, new ArrayList<>(mCurrentTurnDartHits));
|
||||
|
||||
|
||||
// Increment matches played for all players
|
||||
incrementMatchesPlayed();
|
||||
|
||||
|
||||
// Clear turn state
|
||||
mCurrentTurnDarts.clear();
|
||||
mCurrentTurnDartHits.clear();
|
||||
|
||||
|
||||
// Save completed match
|
||||
saveCompletedMatch(winner);
|
||||
|
||||
|
||||
// Mark match as completed
|
||||
mIsMatchCompleted = true;
|
||||
|
||||
|
||||
// Notify UI
|
||||
if (mCallback != null) {
|
||||
mCallback.onPlayerWin(winner, checkoutValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Submits the current turn and advances to the next player.
|
||||
*/
|
||||
public void submitTurn() {
|
||||
// Don't submit if no darts thrown
|
||||
if (mCurrentTurnDarts.isEmpty()) return;
|
||||
|
||||
if (mCurrentTurnDarts.isEmpty())
|
||||
return;
|
||||
|
||||
// Calculate turn total
|
||||
int turnTotal = 0;
|
||||
for (int d : mCurrentTurnDarts) turnTotal += d;
|
||||
|
||||
for (int d : mCurrentTurnDarts)
|
||||
turnTotal += d;
|
||||
|
||||
PlayerState active = mPlayerStates.get(mActivePlayerIndex);
|
||||
|
||||
|
||||
// Calculate final score
|
||||
int finalScore = active.remainingScore - turnTotal;
|
||||
|
||||
|
||||
// Check for 180
|
||||
if (finalScore > 0 && turnTotal == 180) {
|
||||
notifyOneEighty();
|
||||
}
|
||||
|
||||
|
||||
boolean isBust = mIsBustedTurn;
|
||||
|
||||
|
||||
// Update score only if not bust
|
||||
if (!isBust) {
|
||||
active.remainingScore = finalScore;
|
||||
active.dartsThrown += mCurrentTurnDarts.size();
|
||||
}
|
||||
|
||||
|
||||
updatePlayerStats(active, mCurrentTurnDarts.size(), turnTotal, isBust);
|
||||
|
||||
|
||||
// Record dart hits
|
||||
recordTurnHitsToStatistics(active, new ArrayList<>(mCurrentTurnDartHits));
|
||||
|
||||
|
||||
// Rotate to next player
|
||||
mActivePlayerIndex = (mActivePlayerIndex + 1) % mPlayerStates.size();
|
||||
|
||||
|
||||
// Reset turn state
|
||||
mCurrentTurnDarts.clear();
|
||||
mCurrentTurnDartHits.clear();
|
||||
mIsTurnOver = false;
|
||||
mIsBustedTurn = false;
|
||||
|
||||
|
||||
// Save progress
|
||||
saveMatchProgress();
|
||||
|
||||
|
||||
// Notify UI
|
||||
notifyResetVisuals();
|
||||
notifyGameStateChanged();
|
||||
notifyTurnIndicatorsChanged();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the most recently thrown dart from current turn.
|
||||
*/
|
||||
@@ -569,16 +589,16 @@ public class GameManager {
|
||||
if (!mCurrentTurnDarts.isEmpty()) {
|
||||
mCurrentTurnDarts.remove(mCurrentTurnDarts.size() - 1);
|
||||
mCurrentTurnDartHits.remove(mCurrentTurnDartHits.size() - 1);
|
||||
|
||||
|
||||
mIsTurnOver = false;
|
||||
mIsBustedTurn = false;
|
||||
|
||||
|
||||
notifyResetVisuals();
|
||||
notifyTurnIndicatorsChanged();
|
||||
notifyGameStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the current multiplier.
|
||||
*
|
||||
@@ -590,7 +610,7 @@ public class GameManager {
|
||||
mCallback.onMultiplierChanged(multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the current match progress to the database.
|
||||
* Creates a MatchProgress snapshot of the current game state (player scores,
|
||||
@@ -602,18 +622,17 @@ public class GameManager {
|
||||
progress.activePlayerIndex = mActivePlayerIndex;
|
||||
progress.startingScore = mStartingScore;
|
||||
progress.players = new ArrayList<>();
|
||||
|
||||
|
||||
for (PlayerState state : mPlayerStates) {
|
||||
progress.players.add(new MatchProgress.PlayerStateSnapshot(
|
||||
state.playerId,
|
||||
state.name,
|
||||
state.remainingScore,
|
||||
state.dartsThrown
|
||||
));
|
||||
state.dartsThrown));
|
||||
}
|
||||
|
||||
|
||||
String progressJson = MatchProgressConverter.fromProgress(progress);
|
||||
|
||||
|
||||
if (mMatchId > 0) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
@@ -632,7 +651,7 @@ public class GameManager {
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the completed match to the database.
|
||||
* Marks the match as COMPLETED, saves the final game state, and updates
|
||||
@@ -641,32 +660,32 @@ public class GameManager {
|
||||
* @param winner The PlayerState of the winning player (used for logging)
|
||||
*/
|
||||
private void saveCompletedMatch(final PlayerState winner) {
|
||||
if (mMatchId <= 0) return;
|
||||
|
||||
if (mMatchId <= 0)
|
||||
return;
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Match match = mDatabaseHelper.getMatchById(mMatchId);
|
||||
if (match != null) {
|
||||
match.state = Match.MatchState.COMPLETED;
|
||||
match.timestamp = System.currentTimeMillis();
|
||||
|
||||
|
||||
final MatchProgress finalProgress = new MatchProgress();
|
||||
finalProgress.activePlayerIndex = mActivePlayerIndex;
|
||||
finalProgress.startingScore = mStartingScore;
|
||||
finalProgress.players = new ArrayList<>();
|
||||
|
||||
|
||||
for (PlayerState state : mPlayerStates) {
|
||||
finalProgress.players.add(new MatchProgress.PlayerStateSnapshot(
|
||||
state.playerId,
|
||||
state.name,
|
||||
state.remainingScore,
|
||||
state.dartsThrown
|
||||
));
|
||||
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);
|
||||
@@ -676,34 +695,35 @@ public class GameManager {
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates player statistics in the database after a turn.
|
||||
* Convenience method that delegates to the overloaded version with checkoutValue=0.
|
||||
* Convenience method that delegates to the overloaded version with
|
||||
* checkoutValue=0.
|
||||
*
|
||||
* @param active The PlayerState whose statistics should be updated
|
||||
* @param active The PlayerState whose statistics should be updated
|
||||
* @param dartsThrown Number of darts thrown in this turn
|
||||
* @param pointsMade Total points scored in this turn
|
||||
* @param wasBust Whether this turn resulted in a bust
|
||||
* @param pointsMade Total points scored in this turn
|
||||
* @param wasBust Whether this turn resulted in a bust
|
||||
*/
|
||||
private void updatePlayerStats(final PlayerState active, final int dartsThrown, final int pointsMade,
|
||||
final boolean wasBust) {
|
||||
private void updatePlayerStats(final PlayerState active, final int dartsThrown, final int pointsMade,
|
||||
final boolean wasBust) {
|
||||
updatePlayerStats(active, dartsThrown, pointsMade, wasBust, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates player statistics in the database with optional checkout value.
|
||||
* Tracks darts thrown, points made, bust count, and successful checkouts.
|
||||
* Executes asynchronously on a background thread.
|
||||
*
|
||||
* @param active The PlayerState whose statistics should be updated
|
||||
* @param dartsThrown Number of darts thrown in this turn
|
||||
* @param pointsMade Total points scored in this turn
|
||||
* @param wasBust Whether this turn resulted in a bust
|
||||
* @param active The PlayerState whose statistics should be updated
|
||||
* @param dartsThrown Number of darts thrown in this turn
|
||||
* @param pointsMade Total points scored in this turn
|
||||
* @param wasBust Whether this turn resulted in a bust
|
||||
* @param checkoutValue The checkout score if this was a winning turn (0 if not)
|
||||
*/
|
||||
private void updatePlayerStats(final PlayerState active, final int dartsThrown, final int pointsMade,
|
||||
final boolean wasBust, final int checkoutValue) {
|
||||
private void updatePlayerStats(final PlayerState active, final int dartsThrown, final int pointsMade,
|
||||
final boolean wasBust, final int checkoutValue) {
|
||||
if (active.player != null && active.player.id != 0) {
|
||||
new Thread(() -> mDatabaseHelper.updatePlayerStatistics(
|
||||
active.player.id,
|
||||
@@ -711,23 +731,23 @@ public class GameManager {
|
||||
pointsMade,
|
||||
wasBust,
|
||||
checkoutValue,
|
||||
active.dartsThrown
|
||||
)).start();
|
||||
active.dartsThrown)).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tracks a double-out attempt in player statistics.
|
||||
* Records whether a player attempted to finish on a double and whether
|
||||
* they succeeded or missed. Executes asynchronously on a background thread.
|
||||
*
|
||||
* @param playerState The PlayerState of the player who attempted the double
|
||||
* @param isMissed true if the double-out attempt was missed, false if successful
|
||||
* @param isMissed true if the double-out attempt was missed, false if
|
||||
* successful
|
||||
*/
|
||||
private void trackDoubleAttempt(final PlayerState playerState, final boolean isMissed) {
|
||||
new Thread(() -> mDatabaseHelper.trackDoubleAttempt(playerState.playerId, isMissed)).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Increments matches played counter for all players in the current match.
|
||||
* Called when a match is completed to update the match count for all
|
||||
@@ -740,7 +760,7 @@ public class GameManager {
|
||||
}
|
||||
new Thread(() -> mDatabaseHelper.incrementMatchesPlayed(playerIds)).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Records all dart hits from a confirmed turn to player statistics.
|
||||
* Updates the hit distribution map for heat map visualization.
|
||||
@@ -748,19 +768,21 @@ public class GameManager {
|
||||
* Executes asynchronously on a background thread.
|
||||
*
|
||||
* @param playerState The PlayerState whose statistics should be updated
|
||||
* @param dartHits List of DartHit objects representing the darts thrown in this turn
|
||||
* @param dartHits List of DartHit objects representing the darts thrown in
|
||||
* this turn
|
||||
*/
|
||||
private void recordTurnHitsToStatistics(final PlayerState playerState, final List<DartHit> dartHits) {
|
||||
if (dartHits.isEmpty()) return;
|
||||
|
||||
if (dartHits.isEmpty())
|
||||
return;
|
||||
|
||||
final List<DatabaseHelper.DartHit> dbDartHits = new ArrayList<>();
|
||||
for (DartHit hit : dartHits) {
|
||||
dbDartHits.add(new DatabaseHelper.DartHit(hit.baseValue, hit.multiplier));
|
||||
}
|
||||
|
||||
|
||||
new Thread(() -> mDatabaseHelper.recordDartHits(playerState.playerId, dbDartHits)).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resets the game state for a new match.
|
||||
* This clears all current match data but keeps the singleton instance alive.
|
||||
@@ -776,11 +798,11 @@ public class GameManager {
|
||||
mIsBustedTurn = false;
|
||||
mIsMatchCompleted = false;
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================================
|
||||
// Getters for Game State
|
||||
// ========================================================================================
|
||||
|
||||
|
||||
/**
|
||||
* Gets the database ID of the current match.
|
||||
*
|
||||
@@ -789,7 +811,7 @@ public class GameManager {
|
||||
public int getMatchId() {
|
||||
return mMatchId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the starting score for this X01 game.
|
||||
*
|
||||
@@ -798,7 +820,7 @@ public class GameManager {
|
||||
public int getStartingScore() {
|
||||
return mStartingScore;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the currently active player.
|
||||
*
|
||||
@@ -807,7 +829,7 @@ public class GameManager {
|
||||
public int getActivePlayerIndex() {
|
||||
return mActivePlayerIndex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current dart multiplier setting.
|
||||
*
|
||||
@@ -816,7 +838,7 @@ public class GameManager {
|
||||
public int getMultiplier() {
|
||||
return mMultiplier;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a copy of all player states in this match.
|
||||
* Returns a new list to prevent external modification of the internal state.
|
||||
@@ -826,17 +848,18 @@ public class GameManager {
|
||||
public List<PlayerState> getPlayerStates() {
|
||||
return new ArrayList<>(mPlayerStates);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the currently active player's state.
|
||||
*
|
||||
* @return The active PlayerState, or null if no players are loaded
|
||||
*/
|
||||
public PlayerState getActivePlayer() {
|
||||
if (mPlayerStates.isEmpty()) return null;
|
||||
if (mPlayerStates.isEmpty())
|
||||
return null;
|
||||
return mPlayerStates.get(mActivePlayerIndex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a copy of the darts thrown in the current turn.
|
||||
* Returns a new list to prevent external modification.
|
||||
@@ -846,7 +869,7 @@ public class GameManager {
|
||||
public List<Integer> getCurrentTurnDarts() {
|
||||
return new ArrayList<>(mCurrentTurnDarts);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a copy of the detailed dart hits in the current turn.
|
||||
* Returns a new list to prevent external modification.
|
||||
@@ -856,7 +879,7 @@ public class GameManager {
|
||||
public List<DartHit> getCurrentTurnDartHits() {
|
||||
return new ArrayList<>(mCurrentTurnDartHits);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the current turn is over (bust, win, or 3 darts thrown).
|
||||
*
|
||||
@@ -865,7 +888,7 @@ public class GameManager {
|
||||
public boolean isTurnOver() {
|
||||
return mIsTurnOver;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the current turn resulted in a bust.
|
||||
*
|
||||
@@ -874,7 +897,7 @@ public class GameManager {
|
||||
public boolean isBustedTurn() {
|
||||
return mIsBustedTurn;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the match has been completed (someone won).
|
||||
*
|
||||
@@ -883,27 +906,31 @@ public class GameManager {
|
||||
public boolean isMatchCompleted() {
|
||||
return mIsMatchCompleted;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the current target score (remaining score minus current turn darts).
|
||||
* If the turn is busted, returns the remaining score without subtracting bust darts
|
||||
* Calculates the current target score (remaining score minus current turn
|
||||
* darts).
|
||||
* If the turn is busted, returns the remaining score without subtracting bust
|
||||
* darts
|
||||
* since those darts don't count.
|
||||
*
|
||||
* @return The effective remaining score after considering current turn darts
|
||||
*/
|
||||
public int getCurrentTarget() {
|
||||
PlayerState active = getActivePlayer();
|
||||
if (active == null) return 0;
|
||||
|
||||
if (active == null)
|
||||
return 0;
|
||||
|
||||
if (mIsBustedTurn) {
|
||||
return active.remainingScore;
|
||||
}
|
||||
|
||||
|
||||
int turnPointsSoFar = 0;
|
||||
for (int d : mCurrentTurnDarts) turnPointsSoFar += d;
|
||||
for (int d : mCurrentTurnDarts)
|
||||
turnPointsSoFar += d;
|
||||
return active.remainingScore - turnPointsSoFar;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of darts remaining in the current turn.
|
||||
* A turn consists of up to 3 darts.
|
||||
@@ -913,11 +940,11 @@ public class GameManager {
|
||||
public int getDartsRemainingInTurn() {
|
||||
return 3 - mCurrentTurnDarts.size();
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================================
|
||||
// Callback Notification Methods
|
||||
// ========================================================================================
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the UI callback that the general game state has changed.
|
||||
* Used for updating scores, player names, averages, and checkout suggestions.
|
||||
@@ -928,9 +955,10 @@ public class GameManager {
|
||||
mCallback.onGameStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the UI callback that the turn indicators (dart pills) should be updated.
|
||||
* Notifies the UI callback that the turn indicators (dart pills) should be
|
||||
* updated.
|
||||
* Called whenever darts are thrown or undone.
|
||||
* Null-safe - does nothing if no callback is registered.
|
||||
*/
|
||||
@@ -939,7 +967,7 @@ public class GameManager {
|
||||
mCallback.onTurnIndicatorsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the UI callback that a bust has occurred.
|
||||
* Triggers bust animations, sounds, and visual feedback.
|
||||
@@ -950,7 +978,7 @@ public class GameManager {
|
||||
mCallback.onBust();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the UI callback that a perfect 180 was scored.
|
||||
* Triggers celebration animations, sounds, and vibrations.
|
||||
@@ -961,7 +989,7 @@ public class GameManager {
|
||||
mCallback.onOneEightyScored();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the UI callback to reset visual effects (bust overlays, colors).
|
||||
* Called when starting a new turn or undoing darts.
|
||||
|
||||
@@ -14,6 +14,8 @@ import com.aldo.apps.ochecompanion.R;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Match;
|
||||
import com.aldo.apps.ochecompanion.ui.adapter.MainMenuGroupMatchAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Displays summary of most recent match. Adapts display based on match type:
|
||||
* empty state (no matches), 1v1 state (2 players), or group state (3+ players).
|
||||
@@ -35,24 +37,27 @@ public class MatchRecapView extends FrameLayout {
|
||||
private View mStateGroup;
|
||||
|
||||
// ========== 1v1 View References ==========
|
||||
|
||||
|
||||
/** Player 1 name in 1v1 match. */
|
||||
private TextView mTvP1Name;
|
||||
|
||||
|
||||
/** Player 2 name in 1v1 match. */
|
||||
private TextView mTvP2Name;
|
||||
|
||||
|
||||
/** Player 1 score in 1v1 match. */
|
||||
private TextView mTvP1Score;
|
||||
|
||||
|
||||
/** Player 2 score in 1v1 match. */
|
||||
private TextView mTvP2Score;
|
||||
|
||||
// ========== Group View References ==========
|
||||
|
||||
|
||||
/** RecyclerView displaying leaderboard for group matches. */
|
||||
private RecyclerView mRvLeaderboard;
|
||||
|
||||
/** Adapter for group match leaderboard, reused across updates. */
|
||||
private MainMenuGroupMatchAdapter mGroupMatchAdapter;
|
||||
|
||||
/** Constructor for programmatic instantiation. */
|
||||
public MatchRecapView(@NonNull final Context context) {
|
||||
this(context, null);
|
||||
@@ -75,7 +80,7 @@ public class MatchRecapView extends FrameLayout {
|
||||
mTvP1Score = findViewById(R.id.tvP1Score);
|
||||
mTvP2Name = findViewById(R.id.tvP2Name);
|
||||
mTvP2Score = findViewById(R.id.tvP2Score);
|
||||
|
||||
|
||||
mRvLeaderboard = findViewById(R.id.rvLeaderboard);
|
||||
}
|
||||
|
||||
@@ -94,13 +99,34 @@ public class MatchRecapView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds match with pre-enriched participants (e.g., with profile pictures from
|
||||
* database).
|
||||
* Use this when participants have already been enriched to avoid re-parsing
|
||||
* JSON.
|
||||
*/
|
||||
public void setMatchWithParticipants(@Nullable final Match match,
|
||||
@Nullable final List<Match.ParticipantData> enrichedParticipants) {
|
||||
Log.d(TAG, "setMatchWithParticipants() called with: match = [" + match + "]");
|
||||
if (match == null) {
|
||||
updateVisibility(mStateEmpty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.getParticipantCount() > 2) {
|
||||
setupGroupStateWithParticipants(enrichedParticipants);
|
||||
} else {
|
||||
setup1v1State(match);
|
||||
}
|
||||
}
|
||||
|
||||
/** Configures 1v1 state with player names and scores. */
|
||||
private void setup1v1State(final Match match) {
|
||||
updateVisibility(mState1v1);
|
||||
|
||||
|
||||
mTvP1Name.setText(match.getPlayerNameByPosition(0));
|
||||
mTvP1Score.setText(String.valueOf(match.getPlayerScoreByPosition(0)));
|
||||
|
||||
|
||||
mTvP2Name.setText(match.getPlayerNameByPosition(1));
|
||||
mTvP2Score.setText(String.valueOf(match.getPlayerScoreByPosition(1)));
|
||||
}
|
||||
@@ -108,12 +134,31 @@ public class MatchRecapView extends FrameLayout {
|
||||
/** Configures group state with leaderboard RecyclerView. */
|
||||
private void setupGroupState(final Match match) {
|
||||
updateVisibility(mStateGroup);
|
||||
|
||||
mRvLeaderboard.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
|
||||
final MainMenuGroupMatchAdapter adapter = new MainMenuGroupMatchAdapter();
|
||||
mRvLeaderboard.setAdapter(adapter);
|
||||
adapter.updateMatch(match);
|
||||
|
||||
// Initialize adapter and layout manager only once
|
||||
if (mGroupMatchAdapter == null) {
|
||||
mGroupMatchAdapter = new MainMenuGroupMatchAdapter();
|
||||
mRvLeaderboard.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
mRvLeaderboard.setAdapter(mGroupMatchAdapter);
|
||||
}
|
||||
|
||||
// Update the adapter with new match data
|
||||
mGroupMatchAdapter.updateMatch(match);
|
||||
}
|
||||
|
||||
/** Configures group state with pre-enriched participants. */
|
||||
private void setupGroupStateWithParticipants(final List<Match.ParticipantData> enrichedParticipants) {
|
||||
updateVisibility(mStateGroup);
|
||||
|
||||
// Initialize adapter and layout manager only once
|
||||
if (mGroupMatchAdapter == null) {
|
||||
mGroupMatchAdapter = new MainMenuGroupMatchAdapter();
|
||||
mRvLeaderboard.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
mRvLeaderboard.setAdapter(mGroupMatchAdapter);
|
||||
}
|
||||
|
||||
// Update the adapter with enriched participant data
|
||||
mGroupMatchAdapter.updateParticipants(enrichedParticipants);
|
||||
}
|
||||
|
||||
/** Shows only the specified state container, hides others. */
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.aldo.apps.ochecompanion.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -13,11 +14,18 @@ import com.google.android.material.imageview.ShapeableImageView;
|
||||
import com.aldo.apps.ochecompanion.R;
|
||||
|
||||
/**
|
||||
* Reusable MaterialCardView for displaying player info: profile picture, username,
|
||||
* and career statistics. Uses Glide for image loading with fallback to default icon.
|
||||
* Reusable MaterialCardView for displaying player info: profile picture,
|
||||
* username,
|
||||
* and career statistics. Uses Glide for image loading with fallback to default
|
||||
* icon.
|
||||
*/
|
||||
public class PlayerItemView extends MaterialCardView {
|
||||
|
||||
/**
|
||||
* Tag for debugging purposes.
|
||||
*/
|
||||
private static final String TAG = "PlayerItemView";
|
||||
|
||||
/** Player profile picture (circular, loaded via Glide). */
|
||||
private ShapeableImageView mIvAvatar;
|
||||
|
||||
@@ -50,32 +58,35 @@ public class PlayerItemView extends MaterialCardView {
|
||||
mTvStats = findViewById(R.id.tvPlayerAvg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds player data to view components (username, stats, avatar).
|
||||
/**
|
||||
* Binds player data to view components (username, stats, avatar).
|
||||
*
|
||||
* @param player The Player object containing data to display
|
||||
*/
|
||||
public void bind(@NonNull final Player player) {
|
||||
mTvUsername.setText(player.username);
|
||||
mTvStats.setText(String.format(
|
||||
getContext().getString(R.string.txt_player_average_base),
|
||||
getContext().getString(R.string.txt_player_average_base),
|
||||
player.careerAverage));
|
||||
|
||||
if (player.profilePictureUri != null) {
|
||||
Glide.with(getContext())
|
||||
.load(player.profilePictureUri)
|
||||
.into(mIvAvatar);
|
||||
.load(player.profilePictureUri)
|
||||
.into(mIvAvatar);
|
||||
} else {
|
||||
mIvAvatar.setImageResource(R.drawable.ic_users);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds player data along with a specific score (e.g., match score) instead of career average.
|
||||
* Binds player data along with a specific score (e.g., match score) instead of
|
||||
* career average.
|
||||
*
|
||||
* @param player The Player object containing data to display
|
||||
* @param score The specific score to display (e.g., current match score)
|
||||
* @param score The specific score to display (e.g., current match score)
|
||||
*/
|
||||
public void bindWithScore(@NonNull final Player player, final int score) {
|
||||
|
||||
Log.d(TAG, "bindWithScore() called with: player = [" + player + "], score = [" + score + "]");
|
||||
mTvUsername.setText(player.username);
|
||||
// Display match score instead of career average
|
||||
mTvStats.setText(String.valueOf(score));
|
||||
@@ -89,4 +100,71 @@ public class PlayerItemView extends MaterialCardView {
|
||||
}
|
||||
}
|
||||
|
||||
public void bindWithGameAverage(@NonNull final Player player, final double gameAverage) {
|
||||
Log.d(TAG, "bindWithGameAverage() called with: player = [" + player
|
||||
+ "], gameAverage = [" + gameAverage + "]");
|
||||
mTvUsername.setText(player.username);
|
||||
// Display game average
|
||||
mTvStats.setText(String.format(
|
||||
getContext().getString(R.string.txt_player_average_base), gameAverage));
|
||||
|
||||
if (player.profilePictureUri != null) {
|
||||
Glide.with(getContext())
|
||||
.load(player.profilePictureUri)
|
||||
.placeholder(R.drawable.ic_users)
|
||||
.into(mIvAvatar);
|
||||
} else {
|
||||
mIvAvatar.setImageResource(R.drawable.ic_users);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds player data with game average and position indicator.
|
||||
*
|
||||
* @param player The Player object containing data to display
|
||||
* @param gameAverage The game average to display (points per 3 darts)
|
||||
* @param position The player's position (1 = 1st place, 2 = 2nd place, etc.)
|
||||
* @param isWinner Whether this player won (remaining score = 0)
|
||||
*/
|
||||
public void bindWithGameAverageAndPosition(@NonNull final Player player, final double gameAverage,
|
||||
final int position, final boolean isWinner) {
|
||||
Log.d(TAG, "bindWithGameAverageAndPosition() called with: player = [" + player
|
||||
+ "], gameAverage = [" + gameAverage + "], position = [" + position + "], isWinner = [" + isWinner
|
||||
+ "]");
|
||||
|
||||
// Build username with position indicator
|
||||
final String positionSuffix = getPositionSuffix(position);
|
||||
final String displayName = isWinner
|
||||
? "🏆 " + player.username + " (" + positionSuffix + ")"
|
||||
: player.username + " (" + positionSuffix + ")";
|
||||
|
||||
mTvUsername.setText(displayName);
|
||||
|
||||
// Display game average
|
||||
mTvStats.setText(String.format(
|
||||
getContext().getString(R.string.txt_player_average_base), gameAverage));
|
||||
|
||||
if (player.profilePictureUri != null) {
|
||||
Glide.with(getContext())
|
||||
.load(player.profilePictureUri)
|
||||
.placeholder(R.drawable.ic_users)
|
||||
.into(mIvAvatar);
|
||||
} else {
|
||||
mIvAvatar.setImageResource(R.drawable.ic_users);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position suffix (1st, 2nd, 3rd, 4th, etc.)
|
||||
*/
|
||||
private String getPositionSuffix(final int position) {
|
||||
if (position == 1)
|
||||
return "1st";
|
||||
if (position == 2)
|
||||
return "2nd";
|
||||
if (position == 3)
|
||||
return "3rd";
|
||||
return position + "th";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* RecyclerView adapter for displaying group match results in Main Menu.
|
||||
* Displays participants sorted by match score (descending) with their names, scores, and profile pictures.
|
||||
* Displays participants sorted by match score (descending) with their names,
|
||||
* scores, and profile pictures.
|
||||
*/
|
||||
public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGroupMatchAdapter.GroupMatchHolder> {
|
||||
|
||||
@@ -34,7 +35,7 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
/**
|
||||
* Creates a new ViewHolder with a PlayerItemView.
|
||||
*
|
||||
* @param parent The parent ViewGroup.
|
||||
* @param parent The parent ViewGroup.
|
||||
* @param viewType The view type (unused).
|
||||
* @return A new GroupMatchHolder.
|
||||
*/
|
||||
@@ -42,27 +43,28 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
@Override
|
||||
public GroupMatchHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||
// Create a new PlayerItemView for displaying player information
|
||||
final PlayerItemView itemView = new PlayerItemView(parent.getContext());
|
||||
|
||||
final PlayerItemView itemView = new PlayerItemView(parent.getContext());
|
||||
|
||||
// Configure layout parameters for the item view
|
||||
itemView.setLayoutParams(new RecyclerView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
return new GroupMatchHolder(itemView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds player data to the ViewHolder at the specified position.
|
||||
*
|
||||
* @param holder The ViewHolder to update.
|
||||
* @param holder The ViewHolder to update.
|
||||
* @param position The position in the data set.
|
||||
*/
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final GroupMatchHolder holder, final int position) {
|
||||
// Retrieve the participant at this position and bind it to the holder
|
||||
holder.setParticipant(mParticipantsList.get(position));
|
||||
// Position is 0-indexed, but we want 1-indexed for display (1st, 2nd, 3rd,
|
||||
// etc.)
|
||||
holder.setParticipant(mParticipantsList.get(position), position + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +78,8 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the adapter with match data, sorting participants by match score (descending).
|
||||
* Updates the adapter with match data, sorting participants by match score
|
||||
* (descending).
|
||||
*
|
||||
* @param match The match containing participants to display.
|
||||
*/
|
||||
@@ -95,17 +98,46 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
notifyDataSetChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort participants by match score (highest to lowest)
|
||||
participants.sort((p1, p2) -> Integer.compare(p2.score, p1.score));
|
||||
|
||||
|
||||
// Sort participants by remaining score (lowest to highest = best to worst)
|
||||
// In darts, 0 remaining = winner, so lowest score = best placement
|
||||
participants.sort((p1, p2) -> Integer.compare(p1.score, p2.score));
|
||||
|
||||
// Add sorted participants to the display list
|
||||
mParticipantsList.addAll(participants);
|
||||
|
||||
|
||||
// Notify RecyclerView to refresh the display
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the adapter with pre-enriched participant data (e.g., with profile
|
||||
* pictures).
|
||||
* Use this when participants have already been enriched with database data.
|
||||
*
|
||||
* @param participants Pre-enriched participant list to display.
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void updateParticipants(final List<Match.ParticipantData> participants) {
|
||||
// Clear any existing participant data
|
||||
mParticipantsList.clear();
|
||||
|
||||
if (participants == null || participants.isEmpty()) {
|
||||
Log.d(TAG, "updateParticipants: No participants provided, just clearing.");
|
||||
notifyDataSetChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort participants by remaining score (lowest to highest = best to worst)
|
||||
// In darts, 0 remaining = winner, so lowest score = best placement
|
||||
participants.sort((p1, p2) -> Integer.compare(p1.score, p2.score));
|
||||
|
||||
// Add sorted participants to the display list
|
||||
mParticipantsList.addAll(participants);
|
||||
|
||||
// Notify RecyclerView to refresh the display
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewHolder for displaying player items in group match view.
|
||||
@@ -126,20 +158,25 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
public GroupMatchHolder(@NonNull final PlayerItemView itemView) {
|
||||
super(itemView);
|
||||
mItemView = itemView;
|
||||
|
||||
|
||||
// Hide the chevron icon as group match items are not interactive
|
||||
itemView.findViewById(R.id.ivChevron).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds participant data (player + match score) to this ViewHolder.
|
||||
* Binds participant data (player + match score + game average + position) to
|
||||
* this
|
||||
* ViewHolder.
|
||||
*
|
||||
* @param participantData The participant data to display.
|
||||
* @param position The player's position (1 = 1st place, 2 = 2nd place,
|
||||
* etc.)
|
||||
*/
|
||||
public void setParticipant(final Match.ParticipantData participantData) {
|
||||
public void setParticipant(final Match.ParticipantData participantData, final int position) {
|
||||
final Player player = participantData.player;
|
||||
final int score = participantData.score;
|
||||
mItemView.bindWithScore(player, score);
|
||||
final double gameAverage = participantData.gameAverage;
|
||||
final boolean isWinner = participantData.score == 0; // Winner has 0 remaining score
|
||||
mItemView.bindWithGameAverageAndPosition(player, gameAverage, position, isWinner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user