Cleaned up code
This commit is contained in:
@@ -15,6 +15,7 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.aldo.apps.ochecompanion.database.AppDatabase;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||
@@ -77,6 +78,11 @@ public class AddPlayerActivity extends AppCompatActivity {
|
||||
*/
|
||||
private MaterialButton mSaveButton;
|
||||
|
||||
/**
|
||||
* Button to delete a players profile from the database.
|
||||
*/
|
||||
private ImageView mBtnDelete;
|
||||
|
||||
// ========== UI - Cropper Views ==========
|
||||
|
||||
/**
|
||||
@@ -183,6 +189,7 @@ public class AddPlayerActivity extends AppCompatActivity {
|
||||
mUserNameInput = findViewById(R.id.etUsername);
|
||||
mTitleView = findViewById(R.id.tvTitle);
|
||||
mSaveButton = findViewById(R.id.btnSavePlayer);
|
||||
mBtnDelete = findViewById(R.id.btnDeletePlayer);
|
||||
|
||||
// Get references to cropper UI elements
|
||||
mIvCropPreview = findViewById(R.id.ivCropPreview);
|
||||
@@ -191,6 +198,9 @@ public class AddPlayerActivity extends AppCompatActivity {
|
||||
// Set up click listeners
|
||||
mProfilePictureView.setOnClickListener(v -> mGetContent.launch("image/*"));
|
||||
mSaveButton.setOnClickListener(v -> savePlayer());
|
||||
if (mBtnDelete != null) {
|
||||
mBtnDelete.setOnClickListener(v -> deletePlayer());
|
||||
}
|
||||
findViewById(R.id.btnConfirmCrop).setOnClickListener(v -> performCrop());
|
||||
findViewById(R.id.btnCancelCrop).setOnClickListener(v -> exitCropMode());
|
||||
}
|
||||
@@ -202,7 +212,7 @@ public class AddPlayerActivity extends AppCompatActivity {
|
||||
// Initialize scale detector for pinch-to-zoom functionality
|
||||
mScaleDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
@Override
|
||||
public boolean onScale(final ScaleGestureDetector detector) {
|
||||
public boolean onScale(@NonNull final ScaleGestureDetector detector) {
|
||||
// Apply the scale factor from the gesture
|
||||
mScaleFactor *= detector.getScaleFactor();
|
||||
|
||||
@@ -401,6 +411,7 @@ public class AddPlayerActivity extends AppCompatActivity {
|
||||
// Update UI labels for "edit mode"
|
||||
mTitleView.setText(R.string.txt_update_profile_header);
|
||||
mSaveButton.setText(R.string.txt_update_profile_username_save);
|
||||
if (mBtnDelete != null) mBtnDelete.setVisibility(View.VISIBLE);
|
||||
|
||||
// Load existing profile picture if available
|
||||
if (mExistingPlayer.profilePictureUri != null) {
|
||||
@@ -413,6 +424,20 @@ public class AddPlayerActivity extends AppCompatActivity {
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected player from the database.
|
||||
*/
|
||||
private void deletePlayer() {
|
||||
if (mExistingPlayer == null) return;
|
||||
new Thread(() -> {
|
||||
AppDatabase.getDatabase(this).playerDao().delete(mExistingPlayer);
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(this, "Player removed from squad", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and persists the player data to the database.
|
||||
* Inserts new player or updates existing based on mExistingPlayer.
|
||||
@@ -439,7 +464,7 @@ public class AddPlayerActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
// Close activity on main thread after save completes
|
||||
runOnUiThread(() -> finish());
|
||||
runOnUiThread(this::finish);
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,8 @@ import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
||||
import com.aldo.apps.ochecompanion.utils.UIConstants;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Main game activity for playing X01 darts games (501, 301, etc.).
|
||||
@@ -41,10 +40,21 @@ public class GameActivity extends AppCompatActivity {
|
||||
*/
|
||||
private static final String EXTRA_START_SCORE = "extra_start_score";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
|
||||
// ========================================================================================
|
||||
// Game Logic State
|
||||
// ========================================================================================
|
||||
|
||||
/**
|
||||
* The matches UUID from the database.
|
||||
*/
|
||||
private String mMatchUuid;
|
||||
|
||||
/**
|
||||
* Index of the current active player (0 to playerCount-1).
|
||||
* Cycles through players as turns complete.
|
||||
@@ -151,6 +161,7 @@ public class GameActivity extends AppCompatActivity {
|
||||
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());
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -166,6 +177,7 @@ public class GameActivity extends AppCompatActivity {
|
||||
|
||||
// Extract game parameters from intent
|
||||
mStartingScore = getIntent().getIntExtra(EXTRA_START_SCORE, DartsConstants.DEFAULT_GAME_SCORE);
|
||||
mMatchUuid = getIntent().getStringExtra(EXTRA_MATCH_UUID);
|
||||
ArrayList<Player> participants = getIntent().getParcelableArrayListExtra(EXTRA_PLAYERS);
|
||||
|
||||
// Initialize activity components in order
|
||||
@@ -377,6 +389,15 @@ public class GameActivity extends AppCompatActivity {
|
||||
// Update UI for next player
|
||||
updateUI();
|
||||
updateTurnIndicators();
|
||||
saveMatchProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -16,7 +15,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.aldo.apps.ochecompanion.database.AppDatabase;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||
import com.aldo.apps.ochecompanion.models.Match;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Match;
|
||||
import com.aldo.apps.ochecompanion.ui.MatchRecapView;
|
||||
import com.aldo.apps.ochecompanion.ui.adapter.MainMenuPlayerAdapter;
|
||||
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
||||
@@ -156,13 +155,28 @@ public class MainMenuActivity extends AppCompatActivity {
|
||||
private void applyTestData(final int 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 test match objects with different player configurations
|
||||
final Match match1on1 = new Match(playerOne, playerTwo);
|
||||
final Match matchGroup = new Match(playerOne, playerTwo, playerThree, playerFour);
|
||||
// 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);
|
||||
final Match matchGroup = new Match("501", java.util.Arrays.asList(playerOne, playerTwo, playerThree, playerFour), scoresGroup);
|
||||
|
||||
// Cycle through different test scenarios based on counter value
|
||||
if (counter % UIConstants.TEST_CYCLE_MODULO == 0) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||
* @see Player
|
||||
* @see Match
|
||||
*/
|
||||
@Database(entities = {Player.class, Match.class}, version = 2, exportSchema = false)
|
||||
@Database(entities = {Player.class, Match.class}, version = 3, exportSchema = false)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.aldo.apps.ochecompanion.database.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
@@ -34,6 +35,16 @@ public interface PlayerDao {
|
||||
@Update
|
||||
void update(final Player player);
|
||||
|
||||
/**
|
||||
* Deletes an existing player from the database.
|
||||
* Player is identified by its primary key ID.
|
||||
* Must be called on a background thread.
|
||||
*
|
||||
* @param player The player to be deleted.
|
||||
*/
|
||||
@Delete
|
||||
void delete(final Player player);
|
||||
|
||||
/**
|
||||
* Retrieves a player by their unique ID.
|
||||
* Must be called on a background thread.
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
package com.aldo.apps.ochecompanion.database.objects;
|
||||
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.PrimaryKey;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a completed darts match in the Oche Companion application.
|
||||
* Room entity storing match information including game mode, timestamp, player count,
|
||||
* and detailed performance data for all participants. Implements Serializable for
|
||||
* passing between Android components.
|
||||
* passing between Android components. Provides helper methods to parse participant
|
||||
* JSON data and reconstruct Player objects.
|
||||
*
|
||||
* @see com.aldo.apps.ochecompanion.database.dao.MatchDao
|
||||
* @see com.aldo.apps.ochecompanion.database.objects.Player
|
||||
@@ -25,7 +34,7 @@ public class Match implements Serializable {
|
||||
* @see PrimaryKey
|
||||
*/
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public int mId;
|
||||
public int id;
|
||||
|
||||
/**
|
||||
* Unix epoch timestamp (milliseconds) when match was completed.
|
||||
@@ -33,20 +42,20 @@ public class Match implements Serializable {
|
||||
*
|
||||
* @see System#currentTimeMillis()
|
||||
*/
|
||||
public long mTimestamp;
|
||||
public long timestamp;
|
||||
|
||||
/**
|
||||
* Identifier for the darts game variant played (e.g., "501", "301", "Cricket").
|
||||
* Determines scoring rules and UI display for this match.
|
||||
*/
|
||||
public String mGameMode;
|
||||
public String gameMode;
|
||||
|
||||
/**
|
||||
* Total number of players who participated in this match.
|
||||
* Determines match type (1=solo, 2=1v1, 3+=group) and affects UI display.
|
||||
* Must match the number of entries in participantData JSON.
|
||||
*/
|
||||
public int mPlayerCount;
|
||||
public int playerCount;
|
||||
|
||||
/**
|
||||
* JSON string containing detailed performance data for all match participants.
|
||||
@@ -56,7 +65,7 @@ public class Match implements Serializable {
|
||||
* @see org.json.JSONArray
|
||||
* @see org.json.JSONObject
|
||||
*/
|
||||
public String mParticipantData;
|
||||
public String participantData;
|
||||
|
||||
/**
|
||||
* Constructs a new Match entity ready for database insertion.
|
||||
@@ -69,9 +78,219 @@ public class Match implements Serializable {
|
||||
* @see com.aldo.apps.ochecompanion.database.dao.MatchDao#insert(Match)
|
||||
*/
|
||||
public Match(final long timestamp, final String gameMode, final int playerCount, final String participantData) {
|
||||
this.mTimestamp = timestamp;
|
||||
this.mGameMode = gameMode;
|
||||
this.mPlayerCount = playerCount;
|
||||
this.mParticipantData = participantData;
|
||||
this.timestamp = timestamp;
|
||||
this.gameMode = gameMode;
|
||||
this.playerCount = playerCount;
|
||||
this.participantData = participantData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor for creating a Match from a list of Player objects.
|
||||
* Automatically generates JSON participant data and sets timestamp to current time.
|
||||
* All players will have a score of 0.
|
||||
*
|
||||
* @param gameMode Identifier for the darts game variant (e.g., "501", "Cricket")
|
||||
* @param players List of Player objects to include in this match
|
||||
*/
|
||||
@Ignore
|
||||
public Match(final String gameMode, final List<Player> players) {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
this.gameMode = gameMode;
|
||||
this.playerCount = players.size();
|
||||
this.participantData = generateParticipantJson(players, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor for creating a Match from players with their scores.
|
||||
* Automatically generates JSON participant data and sets timestamp to current time.
|
||||
*
|
||||
* @param gameMode Identifier for the darts game variant (e.g., "501", "Cricket")
|
||||
* @param players List of Player objects to include in this match
|
||||
* @param scores Map of player IDs to their final scores
|
||||
*/
|
||||
@Ignore
|
||||
public Match(final String gameMode, final List<Player> players, final Map<Integer, Integer> scores) {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
this.gameMode = gameMode;
|
||||
this.playerCount = players.size();
|
||||
this.participantData = generateParticipantJson(players, scores);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of players participating in this match.
|
||||
*
|
||||
* @return The player count
|
||||
*/
|
||||
public int getParticipantCount() {
|
||||
return playerCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the username of the player at the specified position.
|
||||
*
|
||||
* @param position The zero-based index of the player in the match
|
||||
* @return The username of the player, or "Unknown" if unavailable
|
||||
*/
|
||||
public String getPlayerNameByPosition(final int position) {
|
||||
try {
|
||||
final JSONArray participants = new JSONArray(participantData);
|
||||
if (position >= 0 && position < participants.length()) {
|
||||
final JSONObject participant = participants.getJSONObject(position);
|
||||
return participant.optString("username", "Unknown");
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Return default if JSON parsing fails
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the career average of the player at the specified position.
|
||||
*
|
||||
* @param position The zero-based index of the player in the match
|
||||
* @return The career average, or 0.0 if unavailable
|
||||
*/
|
||||
public double getPlayerAverageByPosition(final int position) {
|
||||
try {
|
||||
final JSONArray participants = new JSONArray(participantData);
|
||||
if (position >= 0 && position < participants.length()) {
|
||||
final JSONObject participant = participants.getJSONObject(position);
|
||||
return participant.optDouble("careerAverage", 0.0);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Return default if JSON parsing fails
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the match score of the player at the specified position.
|
||||
*
|
||||
* @param position The zero-based index of the player in the match
|
||||
* @return The match score, or 0 if unavailable
|
||||
*/
|
||||
public int getPlayerScoreByPosition(final int position) {
|
||||
try {
|
||||
final JSONArray participants = new JSONArray(participantData);
|
||||
if (position >= 0 && position < participants.length()) {
|
||||
final JSONObject participant = participants.getJSONObject(position);
|
||||
return participant.optInt("score", 0);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Return default if JSON parsing fails
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of player IDs to their match scores.
|
||||
*
|
||||
* @return Map of player IDs to scores, or empty map if parsing fails
|
||||
*/
|
||||
public Map<Integer, Integer> getPlayerScores() {
|
||||
final Map<Integer, Integer> scores = new HashMap<>();
|
||||
try {
|
||||
final JSONArray participants = new JSONArray(participantData);
|
||||
for (int i = 0; i < participants.length(); i++) {
|
||||
final JSONObject participant = participants.getJSONObject(i);
|
||||
final int playerId = participant.optInt("id", 0);
|
||||
final int score = participant.optInt("score", 0);
|
||||
scores.put(playerId, score);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Return empty map if JSON parsing fails
|
||||
}
|
||||
return scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all Player objects reconstructed from participant data.
|
||||
*
|
||||
* @return List of Player objects, or empty list if parsing fails
|
||||
*/
|
||||
public List<Player> getAllPlayers() {
|
||||
final List<Player> players = new ArrayList<>();
|
||||
try {
|
||||
final JSONArray participants = new JSONArray(participantData);
|
||||
for (int i = 0; i < participants.length(); i++) {
|
||||
final JSONObject participant = participants.getJSONObject(i);
|
||||
final String username = participant.optString("username", "Unknown");
|
||||
final String photoUri = participant.optString("photoUri", null);
|
||||
final Player player = new Player(username, photoUri);
|
||||
player.id = participant.optInt("id", 0);
|
||||
player.careerAverage = participant.optDouble("careerAverage", 0.0);
|
||||
players.add(player);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Return empty list if JSON parsing fails
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates JSON string from a list of Player objects with optional scores.
|
||||
*
|
||||
* @param players List of Player objects to convert
|
||||
* @param scores Map of player IDs to their match scores (null for all zeros)
|
||||
* @return JSON string representation of player data
|
||||
*/
|
||||
private String generateParticipantJson(final List<Player> players, final Map<Integer, Integer> scores) {
|
||||
final JSONArray participants = new JSONArray();
|
||||
try {
|
||||
for (final Player player : players) {
|
||||
final JSONObject participant = new JSONObject();
|
||||
participant.put("id", player.id);
|
||||
participant.put("username", player.username);
|
||||
participant.put("photoUri", player.profilePictureUri);
|
||||
participant.put("careerAverage", player.careerAverage);
|
||||
final int score = (scores != null && scores.containsKey(player.id)) ? scores.get(player.id) : 0;
|
||||
participant.put("score", score);
|
||||
participants.put(participant);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Return empty array if JSON generation fails
|
||||
}
|
||||
return participants.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class representing a participant in a match with their score.
|
||||
*/
|
||||
public static class ParticipantData {
|
||||
/** The Player object */
|
||||
public final Player player;
|
||||
|
||||
/** The player's score in this match */
|
||||
public final int score;
|
||||
|
||||
public ParticipantData(final Player player, final int score) {
|
||||
this.player = player;
|
||||
this.score = score;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all participants with their match scores.
|
||||
*
|
||||
* @return List of ParticipantData objects containing player info and scores
|
||||
*/
|
||||
public List<ParticipantData> getAllParticipants() {
|
||||
final List<ParticipantData> participants = new ArrayList<>();
|
||||
try {
|
||||
final JSONArray participantArray = new JSONArray(participantData);
|
||||
for (int i = 0; i < participantArray.length(); i++) {
|
||||
final JSONObject participant = participantArray.getJSONObject(i);
|
||||
final String username = participant.optString("username", "Unknown");
|
||||
final String photoUri = participant.optString("photoUri", null);
|
||||
final Player player = new Player(username, photoUri);
|
||||
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));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Return empty list if JSON parsing fails
|
||||
}
|
||||
return participants;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.aldo.apps.ochecompanion.database.objects;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@@ -18,32 +19,32 @@ public class Player implements Parcelable {
|
||||
* Room auto-populates on insert.
|
||||
*/
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public int mId;
|
||||
public int id;
|
||||
|
||||
/**
|
||||
* Player's display name shown throughout the app. Should be non-null and
|
||||
* ideally 1-30 characters. Supports Unicode.
|
||||
*/
|
||||
public String mUsername;
|
||||
public String username;
|
||||
|
||||
/**
|
||||
* File path to player's profile picture. Can be null (shows default avatar).
|
||||
* Stored as path reference to avoid database bloat.
|
||||
*/
|
||||
public String mProfilePictureUri;
|
||||
public String profilePictureUri;
|
||||
|
||||
/**
|
||||
* Player's career three-dart average across all completed matches.
|
||||
* Represents overall skill level. Typical ranges: 0-40 (beginner),
|
||||
* 40-60 (casual), 60-80 (experienced), 80-100 (advanced), 100+ (pro).
|
||||
*/
|
||||
public double mCareerAverage = 0.0;
|
||||
public double careerAverage = 0.0;
|
||||
|
||||
/**
|
||||
* Total number of completed matches for this player. Provides context for
|
||||
* statistical significance and enables experience-based features.
|
||||
*/
|
||||
public int mMatchesPlayed = 0;
|
||||
public int matchesPlayed = 0;
|
||||
|
||||
/**
|
||||
* Constructs a new Player ready for database insertion. ID is auto-generated
|
||||
@@ -53,8 +54,8 @@ public class Player implements Parcelable {
|
||||
* @param profilePictureUri Path to profile image (null for default avatar)
|
||||
*/
|
||||
public Player(final String username, final String profilePictureUri) {
|
||||
this.mUsername = username;
|
||||
this.mProfilePictureUri = profilePictureUri;
|
||||
this.username = username;
|
||||
this.profilePictureUri = profilePictureUri;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,17 +66,17 @@ public class Player implements Parcelable {
|
||||
* @param in Parcel containing serialized Player data
|
||||
*/
|
||||
protected Player(final Parcel in) {
|
||||
mId = in.readInt();
|
||||
mUsername = in.readString();
|
||||
mProfilePictureUri = in.readString();
|
||||
mCareerAverage = in.readDouble();
|
||||
mMatchesPlayed = in.readInt();
|
||||
id = in.readInt();
|
||||
username = in.readString();
|
||||
profilePictureUri = in.readString();
|
||||
careerAverage = in.readDouble();
|
||||
matchesPlayed = in.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Required Parcelable CREATOR field for Player reconstruction.
|
||||
*/
|
||||
public static final Creator<Player> sCREATOR = new Creator<Player>() {
|
||||
public static final Creator<Player> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public Player createFromParcel(final Parcel in) {
|
||||
return new Player(in);
|
||||
@@ -100,11 +101,11 @@ public class Player implements Parcelable {
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeInt(mId);
|
||||
dest.writeString(mUsername);
|
||||
dest.writeString(mProfilePictureUri);
|
||||
dest.writeDouble(mCareerAverage);
|
||||
dest.writeInt(mMatchesPlayed);
|
||||
dest.writeInt(id);
|
||||
dest.writeString(username);
|
||||
dest.writeString(profilePictureUri);
|
||||
dest.writeDouble(careerAverage);
|
||||
dest.writeInt(matchesPlayed);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,13 +115,14 @@ public class Player implements Parcelable {
|
||||
* @return String in format "Player{mId=X, mUsername='...', ...}"
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return "Player{" +
|
||||
"mId=" + mId +
|
||||
", mUsername='" + mUsername + '\'' +
|
||||
", mProfilePictureUri='" + mProfilePictureUri + '\'' +
|
||||
", mCareerAverage=" + mCareerAverage +
|
||||
", mMatchesPlayed=" + mMatchesPlayed +
|
||||
"mId=" + id +
|
||||
", mUsername='" + username + '\'' +
|
||||
", mProfilePictureUri='" + profilePictureUri + '\'' +
|
||||
", mCareerAverage=" + careerAverage +
|
||||
", mMatchesPlayed=" + matchesPlayed +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
package com.aldo.apps.ochecompanion.models;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||
|
||||
/**
|
||||
* Model class representing a darts match with multiple participants.
|
||||
*/
|
||||
public class Match {
|
||||
|
||||
/**
|
||||
* Tag for logging.
|
||||
*/
|
||||
private static final String TAG = "Match";
|
||||
|
||||
/**
|
||||
* List of players participating in this match.
|
||||
*/
|
||||
private final List<Player> mPlayers;
|
||||
|
||||
/**
|
||||
* Constructs an empty Match with no participants.
|
||||
*/
|
||||
public Match() {
|
||||
// Initialize empty player list
|
||||
mPlayers = new ArrayList<>();
|
||||
// Log creation for debugging purposes
|
||||
Log.d(TAG, "Match: Creating new empty match.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Match with the specified players.
|
||||
*
|
||||
* @param players Variable number of Player objects to participate in the match.
|
||||
*/
|
||||
public Match(final Player... players) {
|
||||
// Initialize empty player list
|
||||
mPlayers = new ArrayList<>();
|
||||
|
||||
// Add each player to the match in order
|
||||
for (final Player player : players) {
|
||||
// Log the addition for debugging
|
||||
Log.d(TAG, "Match: Adding [" + player + "]");
|
||||
// Add player to the internal list
|
||||
mPlayers.add(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of players participating in this match.
|
||||
*
|
||||
* @return The number of players in this match.
|
||||
*/
|
||||
public int getParticipantCount() {
|
||||
return mPlayers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the username of the player at the specified position.
|
||||
*
|
||||
* @param position The zero-based index of the player in the match.
|
||||
* @return The username of the player at the specified position, or "INVALID" if the position is out of bounds.
|
||||
*/
|
||||
public String getPlayerNameByPosition(final int position) {
|
||||
// Validate position is within bounds
|
||||
// Note: Consider changing <= to < to prevent IndexOutOfBoundsException
|
||||
if (position >= 0 && position <= mPlayers.size()) {
|
||||
// Return the username of the player at this position
|
||||
return mPlayers.get(position).username;
|
||||
}
|
||||
// Return sentinel value for invalid position
|
||||
return "INVALID";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the career average of the player at the specified position.
|
||||
*
|
||||
* @param position The zero-based index of the player in the match.
|
||||
* @return The career average of the player at the specified position, or -1 if the position is out of bounds.
|
||||
*/
|
||||
public double getPlayerAverageByPosition(final int position) {
|
||||
// Validate position is within bounds
|
||||
// Note: Consider changing <= to < to prevent IndexOutOfBoundsException
|
||||
if (position >= 0 && position <= mPlayers.size()) {
|
||||
// Return the career average of the player at this position
|
||||
return mPlayers.get(position).careerAverage;
|
||||
}
|
||||
// Return sentinel value for invalid position
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all players in this match.
|
||||
*
|
||||
* @return The list of all Player objects in this match.
|
||||
*/
|
||||
public List<Player> getAllPlayers() {
|
||||
return mPlayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this Match.
|
||||
*
|
||||
* @return A string representation of this match including all participating players.
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
// Use StringBuilder for efficient concatenation
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
// Start the match representation
|
||||
sb.append("Match {");
|
||||
|
||||
// Append each player's string representation
|
||||
for (final Player player : mPlayers) {
|
||||
sb.append("[").append(player).append("]");
|
||||
}
|
||||
|
||||
// Close the match representation
|
||||
// Note: This adds "]]" instead of "}". Consider fixing to sb.append("}");
|
||||
sb.append("]");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import android.graphics.Path;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.aldo.apps.ochecompanion.utils.UIConstants;
|
||||
|
||||
@@ -26,9 +28,6 @@ public class CropOverlayView extends View {
|
||||
/** Path with CW outer rect and CCW inner rect creating transparent hole. */
|
||||
private final Path mPath = new Path();
|
||||
|
||||
/** Calculated side length of square crop box (80% of width). */
|
||||
private float mBoxSize;
|
||||
|
||||
/** Constructor for programmatic instantiation. */
|
||||
public CropOverlayView(final Context context) {
|
||||
super(context);
|
||||
@@ -58,10 +57,11 @@ public class CropOverlayView extends View {
|
||||
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
mBoxSize = getWidth() * UIConstants.CROP_BOX_SIZE_RATIO;
|
||||
final float l = (getWidth() - mBoxSize) / 2;
|
||||
final float t = (getHeight() - mBoxSize) / 2;
|
||||
mCropRect.set(l, t, l + mBoxSize, t + mBoxSize);
|
||||
/* Calculated side length of square crop box (80% of width). */
|
||||
final float boxSize = getWidth() * UIConstants.CROP_BOX_SIZE_RATIO;
|
||||
final float l = (getWidth() - boxSize) / 2;
|
||||
final float t = (getHeight() - boxSize) / 2;
|
||||
mCropRect.set(l, t, l + boxSize, t + boxSize);
|
||||
|
||||
mPath.reset();
|
||||
mPath.addRect(0, 0, getWidth(), getHeight(), Path.Direction.CW);
|
||||
@@ -70,7 +70,7 @@ public class CropOverlayView extends View {
|
||||
|
||||
/** Renders the overlay mask with transparent center cutout. */
|
||||
@Override
|
||||
protected void onDraw(final Canvas canvas) {
|
||||
protected void onDraw(@NonNull final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawPath(mPath, mMaskPaint);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.aldo.apps.ochecompanion.R;
|
||||
import com.aldo.apps.ochecompanion.models.Match;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Match;
|
||||
import com.aldo.apps.ochecompanion.ui.adapter.MainMenuGroupMatchAdapter;
|
||||
|
||||
/**
|
||||
@@ -92,10 +92,10 @@ public class MatchRecapView extends FrameLayout {
|
||||
updateVisibility(mState1v1);
|
||||
|
||||
mTvP1Name.setText(match.getPlayerNameByPosition(0));
|
||||
mTvP1Score.setText(String.valueOf(match.getPlayerAverageByPosition(0)));
|
||||
mTvP1Score.setText(String.valueOf(match.getPlayerScoreByPosition(0)));
|
||||
|
||||
mTvP2Name.setText(match.getPlayerNameByPosition(1));
|
||||
mTvP2Score.setText(String.valueOf(match.getPlayerAverageByPosition(1)));
|
||||
mTvP2Score.setText(String.valueOf(match.getPlayerScoreByPosition(1)));
|
||||
}
|
||||
|
||||
/** Configures group state with leaderboard RecyclerView. */
|
||||
|
||||
@@ -11,18 +11,17 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.aldo.apps.ochecompanion.R;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||
import com.aldo.apps.ochecompanion.models.Match;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Match;
|
||||
import com.aldo.apps.ochecompanion.ui.PlayerItemView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* RecyclerView adapter for displaying group match results in Main Menu.
|
||||
* Displays players sorted by career average 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> {
|
||||
|
||||
@@ -30,7 +29,7 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
private static final String TAG = "MainMenuGroupMatchAdapt";
|
||||
|
||||
/** List of players sorted by career average. */
|
||||
private final List<Player> mPlayersList = new ArrayList<>();
|
||||
private final List<Match.ParticipantData> mParticipantsList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new ViewHolder with a PlayerItemView.
|
||||
@@ -62,24 +61,24 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
*/
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final GroupMatchHolder holder, final int position) {
|
||||
// Retrieve the player at this position and bind it to the holder
|
||||
holder.setPlayer(mPlayersList.get(position));
|
||||
// Retrieve the participant at this position and bind it to the holder
|
||||
holder.setParticipant(mParticipantsList.get(position));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of players.
|
||||
* Returns the number of participants.
|
||||
*
|
||||
* @return Player count.
|
||||
* @return Participant count.
|
||||
*/
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mPlayersList.size();
|
||||
return mParticipantsList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the adapter with match data, sorting players by career average.
|
||||
* Updates the adapter with match data, sorting participants by match score (descending).
|
||||
*
|
||||
* @param match The match containing players to display.
|
||||
* @param match The match containing participants to display.
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void updateMatch(final Match match) {
|
||||
@@ -87,23 +86,21 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
Log.d(TAG, "updateMatch: match is null, aborting update.");
|
||||
return;
|
||||
}
|
||||
// Clear any existing player data
|
||||
mPlayersList.clear();
|
||||
// Clear any existing participant data
|
||||
mParticipantsList.clear();
|
||||
|
||||
if (match.getAllPlayers() == null || match.getAllPlayers().isEmpty()) {
|
||||
Log.d(TAG, "updateMatch: No players found in the match, just clearing.");
|
||||
final List<Match.ParticipantData> participants = match.getAllParticipants();
|
||||
if (participants == null || participants.isEmpty()) {
|
||||
Log.d(TAG, "updateMatch: No participants found in the match, just clearing.");
|
||||
notifyDataSetChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all players from the match
|
||||
final List<Player> allPlayers = match.getAllPlayers();
|
||||
// Sort participants by match score (highest to lowest)
|
||||
participants.sort((p1, p2) -> Integer.compare(p2.score, p1.score));
|
||||
|
||||
// Sort players by career average (lowest to highest)
|
||||
allPlayers.sort(new PlayerScoreComparator());
|
||||
|
||||
// Add sorted players to the display list
|
||||
mPlayersList.addAll(allPlayers);
|
||||
// Add sorted participants to the display list
|
||||
mParticipantsList.addAll(participants);
|
||||
|
||||
// Notify RecyclerView to refresh the display
|
||||
notifyDataSetChanged();
|
||||
@@ -143,18 +140,19 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds player data to this ViewHolder.
|
||||
* Binds participant data (player + match score) to this ViewHolder.
|
||||
*
|
||||
* @param player The player to display.
|
||||
* @param participantData The participant data to display.
|
||||
*/
|
||||
public void setPlayer(final Player player) {
|
||||
public void setParticipant(final Match.ParticipantData participantData) {
|
||||
final Player player = participantData.player;
|
||||
final int score = participantData.score;
|
||||
|
||||
// Set player name
|
||||
mPlayerNameView.setText(player.username);
|
||||
|
||||
// Format and set career average score
|
||||
mPlayerScoreView.setText(String.format(
|
||||
itemView.getContext().getString(R.string.txt_player_average_base),
|
||||
player.careerAverage));
|
||||
// Display match score instead of career average
|
||||
mPlayerScoreView.setText(String.valueOf(score));
|
||||
|
||||
// Load profile picture or show default icon
|
||||
if (player.profilePictureUri != null) {
|
||||
@@ -168,23 +166,4 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator for sorting players by career average in ascending order.
|
||||
*/
|
||||
public static class PlayerScoreComparator implements Comparator<Player> {
|
||||
|
||||
/**
|
||||
* Compares two players by career average.
|
||||
*
|
||||
* @param p1 First player.
|
||||
* @param p2 Second player.
|
||||
* @return Comparison result.
|
||||
*/
|
||||
@Override
|
||||
public int compare(final Player p1, final Player p2) {
|
||||
// Compare career averages in ascending order
|
||||
return Double.compare(p1.careerAverage, p2.careerAverage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
app/src/main/res/drawable/ic_delete.xml
Normal file
9
app/src/main/res/drawable/ic_delete.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/double_red"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6V19zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||
</vector>
|
||||
@@ -5,6 +5,19 @@
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/midnight_black">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnDeletePlayer"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_delete"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:visibility="gone"
|
||||
app:tint="@color/double_red" />
|
||||
|
||||
<!-- FORM VIEW -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutForm"
|
||||
|
||||
Reference in New Issue
Block a user