Made use of the DatabaseHelper in all cases now.
Fixed continue logic by moving it to onResume
This commit is contained in:
@@ -9,6 +9,7 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.aldo.apps.ochecompanion.database.DatabaseHelper;
|
||||
import com.aldo.apps.ochecompanion.ui.PlayerStatsView;
|
||||
import com.aldo.apps.ochecompanion.utils.Log;
|
||||
import android.view.MotionEvent;
|
||||
@@ -43,8 +44,10 @@ import java.io.InputStream;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Manages creation and editing of player profiles with username, profile picture, and image cropping.
|
||||
* Operates in Form Mode (profile editing) or Crop Mode (interactive image cropping with pan and zoom).
|
||||
* Manages creation and editing of player profiles with username, profile
|
||||
* picture, and image cropping.
|
||||
* Operates in Form Mode (profile editing) or Crop Mode (interactive image
|
||||
* cropping with pan and zoom).
|
||||
* Pass EXTRA_PLAYER_ID to edit existing player; otherwise creates new player.
|
||||
*/
|
||||
public class AddPlayerActivity extends BaseActivity {
|
||||
@@ -53,39 +56,39 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
* Tag for logging.
|
||||
*/
|
||||
private static final String TAG = "Oche_AddPlayer";
|
||||
|
||||
|
||||
/**
|
||||
* Intent extra key for passing existing player's ID for editing.
|
||||
*/
|
||||
public static final String EXTRA_PLAYER_ID = "extra_player_id";
|
||||
|
||||
// ========== UI - Main Form Views ==========
|
||||
|
||||
|
||||
/**
|
||||
* Container layout for the main player profile form.
|
||||
*/
|
||||
private View mLayoutForm;
|
||||
|
||||
|
||||
/**
|
||||
* Container layout for the image cropping interface.
|
||||
*/
|
||||
private View mLayoutCropper;
|
||||
|
||||
|
||||
/**
|
||||
* ImageView displaying the player's profile picture.
|
||||
*/
|
||||
private ShapeableImageView mProfilePictureView;
|
||||
|
||||
|
||||
/**
|
||||
* EditText field for entering or editing the player's username.
|
||||
*/
|
||||
private EditText mUserNameInput;
|
||||
|
||||
|
||||
/**
|
||||
* TextView displaying the activity title.
|
||||
*/
|
||||
private TextView mTitleView;
|
||||
|
||||
|
||||
/**
|
||||
* Button to save the player profile.
|
||||
*/
|
||||
@@ -102,12 +105,12 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
private ImageView mBtnDelete;
|
||||
|
||||
// ========== UI - Cropper Views ==========
|
||||
|
||||
|
||||
/**
|
||||
* ImageView displaying the full selected image during Crop Mode.
|
||||
*/
|
||||
private ImageView mIvCropPreview;
|
||||
|
||||
|
||||
/**
|
||||
* Custom overlay view that renders the square crop area boundary.
|
||||
*/
|
||||
@@ -124,49 +127,55 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
* Boolean flag indicating whether the stats view is shown.
|
||||
*/
|
||||
private boolean mIsStatsViewShown = false;
|
||||
|
||||
|
||||
/**
|
||||
* Absolute file path to the saved profile picture in internal storage.
|
||||
*/
|
||||
private String mInternalImagePath;
|
||||
|
||||
|
||||
/**
|
||||
* URI of the original image selected from the gallery.
|
||||
*/
|
||||
private Uri mRawSelectedUri;
|
||||
|
||||
|
||||
/**
|
||||
* Database ID of the player being edited (-1 for new player).
|
||||
*/
|
||||
private long mExistingPlayerId = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Player object loaded from the database (null when creating new player).
|
||||
*/
|
||||
private Player mExistingPlayer;
|
||||
|
||||
// ========== Gesture State ==========
|
||||
|
||||
|
||||
/**
|
||||
* Last recorded X coordinate during pan gesture.
|
||||
*/
|
||||
private float mLastTouchX;
|
||||
|
||||
|
||||
/**
|
||||
* Last recorded Y coordinate during pan gesture.
|
||||
*/
|
||||
private float mLastTouchY;
|
||||
|
||||
|
||||
/**
|
||||
* Detector for handling pinch-to-zoom gestures.
|
||||
*/
|
||||
private ScaleGestureDetector mScaleDetector;
|
||||
|
||||
|
||||
/**
|
||||
* Current scale factor applied to the crop preview image (1.0 default, clamped 0.1 to 10.0).
|
||||
* Current scale factor applied to the crop preview image (1.0 default, clamped
|
||||
* 0.1 to 10.0).
|
||||
*/
|
||||
private float mScaleFactor = UIConstants.SCALE_NORMAL;
|
||||
|
||||
/**
|
||||
* Database helper for synchronous database operations.
|
||||
*/
|
||||
private DatabaseHelper mDatabaseHelper;
|
||||
|
||||
/**
|
||||
* ActivityResultLauncher for selecting images from the device gallery.
|
||||
*/
|
||||
@@ -190,7 +199,8 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
});
|
||||
|
||||
/**
|
||||
* Called when the activity is first created. Initializes UI and loads existing player if present.
|
||||
* Called when the activity is first created. Initializes UI and loads existing
|
||||
* player if present.
|
||||
*
|
||||
* @param savedInstanceState Saved instance state.
|
||||
*/
|
||||
@@ -199,16 +209,19 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_add_player);
|
||||
Log.d(TAG, "AddPlayerActivity Created");
|
||||
// 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;
|
||||
});
|
||||
|
||||
mDatabaseHelper = DatabaseHelper.getInstance(this);
|
||||
|
||||
// Initialize all UI components and their click listeners
|
||||
initViews();
|
||||
|
||||
|
||||
// Set up touch gesture handlers for image cropping
|
||||
setupGestures();
|
||||
|
||||
@@ -227,7 +240,8 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the back button press. If the stats view is currently shown, it hides it instead of exiting.
|
||||
* Handles the back button press. If the stats view is currently shown, it hides
|
||||
* it instead of exiting.
|
||||
* Otherwise, it finishes the activity as normal.
|
||||
*/
|
||||
private void handleBackPressed() {
|
||||
@@ -248,7 +262,7 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
// Get references to layout containers
|
||||
mLayoutForm = findViewById(R.id.layoutForm);
|
||||
mLayoutCropper = findViewById(R.id.layoutCropper);
|
||||
|
||||
|
||||
// Get references to form UI elements
|
||||
mProfilePictureView = findViewById(R.id.ivAddPlayerProfile);
|
||||
mUserNameInput = findViewById(R.id.etUsername);
|
||||
@@ -257,7 +271,7 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
mPlayerStatsView = findViewById(R.id.player_stats_view);
|
||||
mSaveButton = findViewById(R.id.btnSavePlayer);
|
||||
mBtnDelete = findViewById(R.id.btnDeletePlayer);
|
||||
|
||||
|
||||
// Get references to cropper UI elements
|
||||
mIvCropPreview = findViewById(R.id.ivCropPreview);
|
||||
mCropOverlay = findViewById(R.id.cropOverlay);
|
||||
@@ -290,7 +304,8 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a rationale dialog explaining why the app needs the specified permission.
|
||||
* Displays a rationale dialog explaining why the app needs the specified
|
||||
* permission.
|
||||
*
|
||||
* @param permission The permission for which to show the rationale
|
||||
*/
|
||||
@@ -298,7 +313,8 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.txt_permission_hint_title)
|
||||
.setMessage(R.string.txt_permission_hint_description)
|
||||
.setPositiveButton(R.string.txt_permission_hint_button_ok, (d, w) -> requestPermissionLauncher.launch(permission))
|
||||
.setPositiveButton(R.string.txt_permission_hint_button_ok,
|
||||
(d, w) -> requestPermissionLauncher.launch(permission))
|
||||
.setNegativeButton(R.string.txt_permission_hint_button_cancel, null)
|
||||
.show();
|
||||
}
|
||||
@@ -313,7 +329,7 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
public boolean onScale(@NonNull final ScaleGestureDetector detector) {
|
||||
// Apply the scale factor from the gesture
|
||||
mScaleFactor *= detector.getScaleFactor();
|
||||
|
||||
|
||||
// Prevent the image from becoming too small or impossibly large
|
||||
// Clamp between 0.1× (10% size) and 10.0× (1000% size)
|
||||
mScaleFactor = Math.max(UIConstants.SCALE_MIN_ZOOM, Math.min(mScaleFactor, UIConstants.SCALE_MAX_ZOOM));
|
||||
@@ -342,11 +358,11 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
// Calculate movement delta
|
||||
float dx = event.getRawX() - mLastTouchX;
|
||||
float dy = event.getRawY() - mLastTouchY;
|
||||
|
||||
|
||||
// Apply translation to the view
|
||||
v.setTranslationX(v.getTranslationX() + dx);
|
||||
v.setTranslationY(v.getTranslationY() + dy);
|
||||
|
||||
|
||||
// Update last touch position for next delta calculation
|
||||
mLastTouchX = event.getRawX();
|
||||
mLastTouchY = event.getRawY();
|
||||
@@ -368,11 +384,11 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
mLayoutCropper.setVisibility(View.VISIBLE);
|
||||
|
||||
// Reset transformation state for a fresh start
|
||||
mScaleFactor = UIConstants.SCALE_NORMAL; // Reset zoom to 100%
|
||||
mScaleFactor = UIConstants.SCALE_NORMAL; // Reset zoom to 100%
|
||||
mIvCropPreview.setScaleX(UIConstants.SCALE_NORMAL);
|
||||
mIvCropPreview.setScaleY(UIConstants.SCALE_NORMAL);
|
||||
mIvCropPreview.setTranslationX(0); // Reset horizontal position
|
||||
mIvCropPreview.setTranslationY(0); // Reset vertical position
|
||||
mIvCropPreview.setTranslationX(0); // Reset horizontal position
|
||||
mIvCropPreview.setTranslationY(0); // Reset vertical position
|
||||
|
||||
// Load the selected image into the preview
|
||||
mIvCropPreview.setImageURI(uri);
|
||||
@@ -388,7 +404,8 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the pixel-level mathematics to extract a square crop from the selected image.
|
||||
* Performs the pixel-level mathematics to extract a square crop from the
|
||||
* selected image.
|
||||
* Accounts for ImageView fit-center scale, user translation, and user zoom.
|
||||
*/
|
||||
private void performCrop() {
|
||||
@@ -429,32 +446,35 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
|
||||
Log.d(TAG, String.format("Crop Pixels: X=%d, Y=%d, Size=%d | UserZoom=%.2f", cX, cY, cSize, mScaleFactor));
|
||||
|
||||
// Bounds checks to prevent Bitmap.createBitmap from crashing with invalid coordinates
|
||||
cX = Math.max(0, cX); // Ensure X is not negative
|
||||
cY = Math.max(0, cY); // Ensure Y is not negative
|
||||
|
||||
// Bounds checks to prevent Bitmap.createBitmap from crashing with invalid
|
||||
// coordinates
|
||||
cX = Math.max(0, cX); // Ensure X is not negative
|
||||
cY = Math.max(0, cY); // Ensure Y is not negative
|
||||
|
||||
// Clamp crop size to not exceed bitmap boundaries
|
||||
if (cX + cSize > bmpW) cSize = (int) bmpW - cX;
|
||||
if (cY + cSize > bmpH) cSize = (int) bmpH - cY;
|
||||
if (cX + cSize > bmpW)
|
||||
cSize = (int) bmpW - cX;
|
||||
if (cY + cSize > bmpH)
|
||||
cSize = (int) bmpH - cY;
|
||||
|
||||
// Ensure size is at least 1px to avoid crashes
|
||||
cSize = Math.max(1, cSize);
|
||||
|
||||
// Extract the square crop from the full bitmap
|
||||
Bitmap cropped = Bitmap.createBitmap(fullBmp, cX, cY, cSize, cSize);
|
||||
|
||||
|
||||
// Save the cropped bitmap to internal storage
|
||||
mInternalImagePath = saveBitmap(cropped);
|
||||
|
||||
// Update the profile picture preview if save was successful
|
||||
if (mInternalImagePath != null) {
|
||||
mProfilePictureView.setImageTintList(null); // Remove any tint
|
||||
mProfilePictureView.setImageTintList(null); // Remove any tint
|
||||
mProfilePictureView.setImageBitmap(cropped);
|
||||
}
|
||||
|
||||
// Return to Form Mode
|
||||
exitCropMode();
|
||||
|
||||
|
||||
// Clean up the full bitmap to free memory
|
||||
fullBmp.recycle();
|
||||
|
||||
@@ -465,7 +485,8 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a bitmap to the application's private internal storage as JPEG with 90% quality.
|
||||
* Saves a bitmap to the application's private internal storage as JPEG with 90%
|
||||
* quality.
|
||||
*
|
||||
* @param bmp The bitmap image to save.
|
||||
* @return The absolute file path, or null if saving failed.
|
||||
@@ -474,15 +495,15 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
try {
|
||||
// Generate a unique filename using UUID to prevent collisions
|
||||
String name = "profile_" + UUID.randomUUID().toString() + ".jpg";
|
||||
|
||||
|
||||
// Create file reference in app's private directory
|
||||
File file = new File(getFilesDir(), name);
|
||||
|
||||
|
||||
// Write bitmap to file as JPEG with 90% quality (good balance of quality/size)
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
bmp.compress(Bitmap.CompressFormat.JPEG, UIConstants.JPEG_QUALITY, fos);
|
||||
}
|
||||
|
||||
|
||||
// Return the absolute path for database storage
|
||||
return file.getAbsolutePath();
|
||||
} catch (Exception e) {
|
||||
@@ -498,20 +519,22 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
private void loadExistingPlayer() {
|
||||
new Thread(() -> {
|
||||
// Query the database for the player (background thread)
|
||||
mExistingPlayer = AppDatabase.getDatabase(this).playerDao().getPlayerById(mExistingPlayerId);
|
||||
final Statistics statistics = AppDatabase.getDatabase(this).statisticsDao().getStatisticsForPlayer(mExistingPlayerId);
|
||||
|
||||
mExistingPlayer = mDatabaseHelper.getPlayerById(mExistingPlayerId);
|
||||
final Statistics statistics = mDatabaseHelper.getStatisticsForPlayer(mExistingPlayerId);
|
||||
|
||||
// Update UI on the main thread
|
||||
runOnUiThread(() -> {
|
||||
if (mExistingPlayer != null) {
|
||||
// Populate username field
|
||||
mUserNameInput.setText(mExistingPlayer.username);
|
||||
|
||||
|
||||
// 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);
|
||||
if (mShowStatsButton != null) mShowStatsButton.setVisibility(View.VISIBLE);
|
||||
if (mBtnDelete != null)
|
||||
mBtnDelete.setVisibility(View.VISIBLE);
|
||||
if (mShowStatsButton != null)
|
||||
mShowStatsButton.setVisibility(View.VISIBLE);
|
||||
mShowStatsButton.setOnClickListener(v -> {
|
||||
mPlayerStatsView.setVisibility(View.VISIBLE);
|
||||
mIsStatsViewShown = true;
|
||||
@@ -519,11 +542,11 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
if (statistics != null && mPlayerStatsView != null) {
|
||||
mPlayerStatsView.bind(mExistingPlayer, statistics);
|
||||
}
|
||||
|
||||
|
||||
// Load existing profile picture if available
|
||||
if (mExistingPlayer.profilePictureUri != null) {
|
||||
mInternalImagePath = mExistingPlayer.profilePictureUri;
|
||||
mProfilePictureView.setImageTintList(null); // Remove placeholder tint
|
||||
mProfilePictureView.setImageTintList(null); // Remove placeholder tint
|
||||
mProfilePictureView.setImageURI(Uri.fromFile(new File(mInternalImagePath)));
|
||||
}
|
||||
}
|
||||
@@ -534,12 +557,14 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
/**
|
||||
* Deletes the currently loaded player from the database.
|
||||
* Shows confirmation toast and closes the activity upon successful deletion.
|
||||
* Runs database operation on background thread. Does nothing if no player is loaded.
|
||||
* Runs database operation on background thread. Does nothing if no player is
|
||||
* loaded.
|
||||
*/
|
||||
private void deletePlayer() {
|
||||
if (mExistingPlayer == null) return;
|
||||
if (mExistingPlayer == null)
|
||||
return;
|
||||
new Thread(() -> {
|
||||
AppDatabase.getDatabase(this).playerDao().delete(mExistingPlayer);
|
||||
mDatabaseHelper.deletePlayer(mExistingPlayer);
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(this, "Player removed from squad", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
@@ -565,19 +590,19 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
// Update existing player
|
||||
mExistingPlayer.username = name;
|
||||
mExistingPlayer.profilePictureUri = mInternalImagePath;
|
||||
AppDatabase.getDatabase(this).playerDao().update(mExistingPlayer);
|
||||
mDatabaseHelper.updatePlayer(mExistingPlayer);
|
||||
} else {
|
||||
// Create and insert new player
|
||||
final Player p = new Player(name, mInternalImagePath);
|
||||
final long newUserId = AppDatabase.getDatabase(this).playerDao().insert(p);
|
||||
final Player dbPlayer = AppDatabase.getDatabase(this).playerDao().getPlayerById(newUserId);
|
||||
final long newUserId = mDatabaseHelper.insertPlayer(p);
|
||||
final Player dbPlayer = mDatabaseHelper.getPlayerById(newUserId);
|
||||
if (dbPlayer != null) {
|
||||
Log.d(TAG, "savePlayer: Player has been created, create stats as well.");
|
||||
final Statistics playerStats = new Statistics(dbPlayer.id);
|
||||
AppDatabase.getDatabase(this).statisticsDao().insertStatistics(playerStats);
|
||||
mDatabaseHelper.insertStats(playerStats);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Close activity on main thread after save completes
|
||||
runOnUiThread(this::finish);
|
||||
}).start();
|
||||
|
||||
@@ -62,6 +62,9 @@ import nl.dionsegijn.konfetti.xml.KonfettiView;
|
||||
*/
|
||||
public class GameActivity extends BaseActivity implements GameManager.GameStateCallback {
|
||||
|
||||
/**
|
||||
* Tag for logging purposes.
|
||||
*/
|
||||
private static final String TAG = "GameActivity";
|
||||
|
||||
/**
|
||||
|
||||
@@ -86,21 +86,8 @@ public class MainMenuActivity extends BaseActivity {
|
||||
return insets;
|
||||
});
|
||||
|
||||
final QuickStartButton quickStartBtn = findViewById(R.id.quick_start_btn);
|
||||
final String defaultGameMode = mSettingsPref.getString(getString(R.string.pref_desc_standard_game_mode),
|
||||
getString(R.string.pref_game_mode_501_value));
|
||||
quickStartBtn.setSubText(defaultGameMode);
|
||||
quickStartBtn.setOnClickListener(v -> quickStart());
|
||||
findViewById(R.id.btnSettings).setOnClickListener(v -> launchSettings());
|
||||
|
||||
final List<Match> ongoingMatches = mDatabaseHelper.getOngoingMatches();
|
||||
if (ongoingMatches != null && !ongoingMatches.isEmpty()) {
|
||||
mOngoingMatch = ongoingMatches.get(0);
|
||||
}
|
||||
if (mOngoingMatch != null) {
|
||||
Log.d(TAG, "onCreate: Found ongoing match [" + mOngoingMatch + "]");
|
||||
quickStartBtn.setSubText("Continue match with " + mOngoingMatch.gameMode + " score");
|
||||
}
|
||||
findViewById(R.id.btnSettings).setOnClickListener(v -> launchSettings());
|
||||
|
||||
// Set up match recap view with test data functionality
|
||||
mMatchRecap = findViewById(R.id.match_recap);
|
||||
@@ -122,6 +109,19 @@ public class MainMenuActivity extends BaseActivity {
|
||||
initSquadView();
|
||||
// Apply the last finished match if available.
|
||||
applyLastMatch();
|
||||
final QuickStartButton quickStartBtn = findViewById(R.id.quick_start_btn);
|
||||
final String defaultGameMode = mSettingsPref.getString(getString(R.string.pref_desc_standard_game_mode),
|
||||
getString(R.string.pref_game_mode_501_value));
|
||||
quickStartBtn.setSubText(defaultGameMode);
|
||||
quickStartBtn.setOnClickListener(v -> quickStart());
|
||||
final List<Match> ongoingMatches = mDatabaseHelper.getOngoingMatches();
|
||||
if (ongoingMatches != null && !ongoingMatches.isEmpty()) {
|
||||
mOngoingMatch = ongoingMatches.get(0);
|
||||
}
|
||||
if (mOngoingMatch != null) {
|
||||
Log.d(TAG, "onCreate: Found ongoing match [" + mOngoingMatch + "]");
|
||||
quickStartBtn.setSubText("Continue match with " + mOngoingMatch.gameMode + " score");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,10 +17,14 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Centralized database helper that manages all database operations with proper synchronization.
|
||||
* Handles threading internally to prevent race conditions and simplify database access.
|
||||
* All database operations are executed on a background thread pool with per-player locking
|
||||
* to ensure data integrity while allowing concurrent updates for different players.
|
||||
* Centralized database helper that manages all database operations with proper
|
||||
* synchronization.
|
||||
* Handles threading internally to prevent race conditions and simplify database
|
||||
* access.
|
||||
* All database operations are executed on a background thread pool with
|
||||
* per-player locking
|
||||
* to ensure data integrity while allowing concurrent updates for different
|
||||
* players.
|
||||
*/
|
||||
public class DatabaseHelper {
|
||||
|
||||
@@ -40,14 +44,16 @@ public class DatabaseHelper {
|
||||
private final AppDatabase mDatabase;
|
||||
|
||||
/**
|
||||
* Single-threaded executor for database operations to ensure sequential execution.
|
||||
* Single-threaded executor for database operations to ensure sequential
|
||||
* execution.
|
||||
* Prevents race conditions by serializing all database writes.
|
||||
*/
|
||||
private final ExecutorService mExecutor;
|
||||
|
||||
/**
|
||||
* Per-player locks for fine-grained synchronization.
|
||||
* Allows concurrent updates for different players while preventing conflicts for the same player.
|
||||
* Allows concurrent updates for different players while preventing conflicts
|
||||
* for the same player.
|
||||
*/
|
||||
private final Map<Long, Object> mPlayerLocks = new HashMap<>();
|
||||
|
||||
@@ -95,15 +101,17 @@ public class DatabaseHelper {
|
||||
* Updates player statistics after a turn.
|
||||
* Handles darts thrown, points made, bust tracking, and milestone counting.
|
||||
*
|
||||
* @param playerId Player's database ID
|
||||
* @param dartsThrown Number of darts thrown this turn
|
||||
* @param pointsMade Total points scored this turn
|
||||
* @param wasBust Whether the turn resulted in a bust
|
||||
* @param checkoutValue The checkout score if this was a winning turn (0 if not a checkout)
|
||||
* @param totalDartsThrownInMatch Total darts thrown by player in current match (for first 9 tracking)
|
||||
* @param playerId Player's database ID
|
||||
* @param dartsThrown Number of darts thrown this turn
|
||||
* @param pointsMade Total points scored this turn
|
||||
* @param wasBust Whether the turn resulted in a bust
|
||||
* @param checkoutValue The checkout score if this was a winning turn
|
||||
* (0 if not a checkout)
|
||||
* @param totalDartsThrownInMatch Total darts thrown by player in current match
|
||||
* (for first 9 tracking)
|
||||
*/
|
||||
public void updatePlayerStatistics(final long playerId, final int dartsThrown, final int pointsMade,
|
||||
final boolean wasBust, final int checkoutValue, final int totalDartsThrownInMatch) {
|
||||
public void updatePlayerStatistics(final long playerId, final int dartsThrown, final int pointsMade,
|
||||
final boolean wasBust, final int checkoutValue, final int totalDartsThrownInMatch) {
|
||||
mExecutor.execute(() -> {
|
||||
final Object lock = getPlayerLock(playerId);
|
||||
synchronized (lock) {
|
||||
@@ -122,7 +130,8 @@ public class DatabaseHelper {
|
||||
} else {
|
||||
// Normal turn - record darts and points
|
||||
playerStats.saveDartsThrown(dartsThrown, pointsMade);
|
||||
Log.d(TAG, "updatePlayerStatistics: dartsThrown = [" + dartsThrown + "], pointsMade = [" + pointsMade + "]");
|
||||
Log.d(TAG, "updatePlayerStatistics: dartsThrown = [" + dartsThrown + "], pointsMade = ["
|
||||
+ pointsMade + "]");
|
||||
|
||||
// Track missed darts if turn ended early (less than 3 darts)
|
||||
if (dartsThrown < 3) {
|
||||
@@ -147,7 +156,8 @@ public class DatabaseHelper {
|
||||
final long dartsToAdd = Math.min(dartsThrown, 9 - totalDartsThrownInMatch);
|
||||
playerStats.setTotalFirst9Darts(playerStats.getTotalFirst9Darts() + dartsToAdd);
|
||||
playerStats.setTotalFirst9Points(playerStats.getTotalFirst9Points() + pointsMade);
|
||||
Log.d(TAG, "updatePlayerStatistics: First 9 tracking - darts: " + dartsToAdd + ", points: " + pointsMade);
|
||||
Log.d(TAG, "updatePlayerStatistics: First 9 tracking - darts: " + dartsToAdd + ", points: "
|
||||
+ pointsMade);
|
||||
}
|
||||
|
||||
// Track successful checkout
|
||||
@@ -194,7 +204,8 @@ public class DatabaseHelper {
|
||||
if (playerStats != null) {
|
||||
playerStats.addDoubleOutTarget(isMissed);
|
||||
mDatabase.statisticsDao().updateStatistics(playerStats);
|
||||
Log.d(TAG, "trackDoubleAttempt: Recorded double attempt (missed=" + isMissed + ") for player " + playerId);
|
||||
Log.d(TAG, "trackDoubleAttempt: Recorded double attempt (missed=" + isMissed + ") for player "
|
||||
+ playerId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "trackDoubleAttempt: Failed to track double attempt", e);
|
||||
@@ -219,7 +230,8 @@ public class DatabaseHelper {
|
||||
if (playerStats != null) {
|
||||
playerStats.addCompletedMatch();
|
||||
mDatabase.statisticsDao().updateStatistics(playerStats);
|
||||
Log.d(TAG, "incrementMatchesPlayed: Incremented for player " + playerId + ", total: " + playerStats.getMatchesPlayed());
|
||||
Log.d(TAG, "incrementMatchesPlayed: Incremented for player " + playerId + ", total: "
|
||||
+ playerStats.getMatchesPlayed());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "incrementMatchesPlayed: Failed to increment matches for player " + playerId, e);
|
||||
@@ -246,7 +258,7 @@ public class DatabaseHelper {
|
||||
/**
|
||||
* 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) {
|
||||
@@ -263,7 +275,8 @@ public class DatabaseHelper {
|
||||
* @param dartHits List of dart hit details from the turn
|
||||
*/
|
||||
public void recordDartHits(final long playerId, final List<DartHit> dartHits) {
|
||||
if (dartHits == null || dartHits.isEmpty()) return;
|
||||
if (dartHits == null || dartHits.isEmpty())
|
||||
return;
|
||||
|
||||
mExecutor.execute(() -> {
|
||||
final Object lock = getPlayerLock(playerId);
|
||||
@@ -271,12 +284,14 @@ public class DatabaseHelper {
|
||||
try {
|
||||
final Statistics playerStats = mDatabase.statisticsDao().getStatisticsForPlayer(playerId);
|
||||
if (playerStats != null) {
|
||||
Log.d(TAG, "recordDartHits: Before recording - hitDistribution size: " + playerStats.getHitDistribution().size());
|
||||
Log.d(TAG, "recordDartHits: Before recording - hitDistribution size: "
|
||||
+ playerStats.getHitDistribution().size());
|
||||
// Record all darts from this turn
|
||||
for (final DartHit hit : dartHits) {
|
||||
playerStats.recordDartHit(hit.baseValue, hit.multiplier);
|
||||
}
|
||||
Log.d(TAG, "recordDartHits: After recording - hitDistribution size: " + playerStats.getHitDistribution().size());
|
||||
Log.d(TAG, "recordDartHits: After recording - hitDistribution size: "
|
||||
+ playerStats.getHitDistribution().size());
|
||||
Log.d(TAG, "recordDartHits: hitDistribution contents: " + playerStats.getHitDistribution());
|
||||
mDatabase.statisticsDao().updateStatistics(playerStats);
|
||||
Log.d(TAG, "recordDartHits: Recorded " + dartHits.size() + " darts for player " + playerId);
|
||||
@@ -290,7 +305,8 @@ public class DatabaseHelper {
|
||||
|
||||
/**
|
||||
* Retrieves all players from the database synchronously.
|
||||
* Blocks until the operation completes to ensure consistency with any pending writes.
|
||||
* Blocks until the operation completes to ensure consistency with any pending
|
||||
* writes.
|
||||
*
|
||||
* @return List of all players, or empty list if none exist
|
||||
*/
|
||||
@@ -304,12 +320,13 @@ public class DatabaseHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new match record in the database with the specified game mode and players.
|
||||
* Creates a new match record in the database with the specified game mode and
|
||||
* players.
|
||||
* Initializes the match progress with starting scores and player data.
|
||||
* Blocks until the operation completes to return the new match ID.
|
||||
*
|
||||
* @param gameMode The game mode string (e.g., "501")
|
||||
* @param players List of Player objects participating in the match
|
||||
* @param players List of Player objects participating in the match
|
||||
* @return The ID of the newly created match, or -1 if creation failed
|
||||
*/
|
||||
public long createNewMatch(final String gameMode, final List<Player> players) {
|
||||
@@ -320,13 +337,13 @@ public class DatabaseHelper {
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "createNewMatch: Could not parse gameMode as integer, using default 501");
|
||||
}
|
||||
|
||||
|
||||
// Create initial MatchProgress with player data
|
||||
final MatchProgress initialProgress = new MatchProgress();
|
||||
initialProgress.activePlayerIndex = 0; // First player starts
|
||||
initialProgress.startingScore = startingScore;
|
||||
initialProgress.players = new ArrayList<>();
|
||||
|
||||
|
||||
// Create player state snapshots with initial values
|
||||
if (players != null && !players.isEmpty()) {
|
||||
for (Player player : players) {
|
||||
@@ -343,14 +360,13 @@ public class DatabaseHelper {
|
||||
0L, // Guest has ID 0
|
||||
"GUEST",
|
||||
startingScore,
|
||||
0
|
||||
));
|
||||
0));
|
||||
}
|
||||
|
||||
|
||||
// Convert to JSON
|
||||
final String participantData = MatchProgressConverter.fromProgress(initialProgress);
|
||||
|
||||
final Match match = new Match(System.currentTimeMillis(), gameMode,
|
||||
|
||||
final Match match = new Match(System.currentTimeMillis(), gameMode,
|
||||
initialProgress.players.size(), participantData, Match.MatchState.ONGOING);
|
||||
try {
|
||||
return mExecutor.submit(() -> {
|
||||
@@ -384,7 +400,9 @@ public class DatabaseHelper {
|
||||
|
||||
/**
|
||||
* Retrieves all ongoing matches from the database synchronously.
|
||||
* Blocks until the operation completes to ensure consistency with any pending writes.
|
||||
* Blocks until the operation completes to ensure consistency with any pending
|
||||
* writes.
|
||||
*
|
||||
* @return List of ongoing matches, or empty list if none exist
|
||||
*/
|
||||
public List<Match> getOngoingMatches() {
|
||||
@@ -419,8 +437,11 @@ public class DatabaseHelper {
|
||||
|
||||
/**
|
||||
* Retrieves the most recently completed match from the database synchronously.
|
||||
* Blocks until the operation completes to ensure consistency with any pending writes.
|
||||
* @return The most recent completed match, or null if no completed matches exist
|
||||
* Blocks until the operation completes to ensure consistency with any pending
|
||||
* writes.
|
||||
*
|
||||
* @return The most recent completed match, or null if no completed matches
|
||||
* exist
|
||||
*/
|
||||
public Match getLastCompletedMatch() {
|
||||
try {
|
||||
@@ -438,9 +459,34 @@ public class DatabaseHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the participant data from the most recent match in the database.
|
||||
* This method is synchronous and blocks until the database operation completes.
|
||||
*
|
||||
* @return The participant data (JSON string) from the most recent match,
|
||||
* or null if no matches are found.
|
||||
*/
|
||||
public String getLastMatchParticipantData() {
|
||||
try {
|
||||
return mExecutor.submit(() -> {
|
||||
try {
|
||||
return mDatabase.matchDao().getLastMatchParticipantData();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getLastMatchParticipantData: Failed to retrieve last match participant data", e);
|
||||
return null;
|
||||
}
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getLastMatchParticipantData: Failed to submit task", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a match by its unique ID from the database synchronously.
|
||||
* Blocks until the operation completes to ensure consistency with any pending writes.
|
||||
* Blocks until the operation completes to ensure consistency with any pending
|
||||
* writes.
|
||||
*
|
||||
* @param matchId The unique identifier of the match
|
||||
* @return The match with the specified ID, or null if no such match exists
|
||||
*/
|
||||
@@ -462,7 +508,8 @@ public class DatabaseHelper {
|
||||
|
||||
/**
|
||||
* Retrieves statistics for a specific player synchronously.
|
||||
* Blocks until the operation completes to ensure consistency with any pending writes.
|
||||
* Blocks until the operation completes to ensure consistency with any pending
|
||||
* writes.
|
||||
*
|
||||
* @param playerId The player's database ID
|
||||
* @return Statistics object for the player, or null if not found
|
||||
@@ -486,6 +533,89 @@ public class DatabaseHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new player into the database.
|
||||
*
|
||||
* @param player The Player entity to insert
|
||||
*/
|
||||
public long insertPlayer(final Player player) {
|
||||
try {
|
||||
return mExecutor.submit(() -> mDatabase.playerDao().insert(player)).get();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "insertPlayer: Failed to submit task", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing player in the database.
|
||||
*
|
||||
* @param player The Player entity to update
|
||||
*/
|
||||
public void updatePlayer(final Player player) {
|
||||
mExecutor.execute(() -> {
|
||||
try {
|
||||
mDatabase.playerDao().update(player);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "updatePlayer: Failed to update player", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a player from the database.
|
||||
*
|
||||
* @param player The Player entity to delete
|
||||
*/
|
||||
public void deletePlayer(final Player player) {
|
||||
mExecutor.execute(() -> {
|
||||
try {
|
||||
mDatabase.playerDao().delete(player);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "deletePlayer: Failed to delete player", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a player by their database ID synchronously.
|
||||
* Blocks until the operation completes to ensure consistency with any pending
|
||||
* writes.
|
||||
*
|
||||
* @param playerId The player's database ID
|
||||
* @return Player object for the specified player, or null if not found
|
||||
*/
|
||||
public Player getPlayerById(final long playerId) {
|
||||
try {
|
||||
return mExecutor.submit(() -> {
|
||||
try {
|
||||
return mDatabase.playerDao().getPlayerById(playerId);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getPlayerById: Failed to retrieve player", e);
|
||||
return null;
|
||||
}
|
||||
}).get();
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "getPlayerById: Failed to submit task", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a PlayerStats entity into the database.
|
||||
*
|
||||
* @param playerStats The PlayerStats entity to insert
|
||||
*/
|
||||
public void insertStats(final Statistics playerStats) {
|
||||
mExecutor.execute(() -> {
|
||||
try {
|
||||
mDatabase.statisticsDao().insertStatistics(playerStats);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "insertStats: Failed to insert stats", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the executor service.
|
||||
* Should be called when the helper is no longer needed.
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import com.aldo.apps.ochecompanion.utils.Log;
|
||||
import com.aldo.apps.ochecompanion.utils.MatchProgress;
|
||||
import com.aldo.apps.ochecompanion.utils.converters.MatchProgressConverter;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.aldo.apps.ochecompanion.GameActivity;
|
||||
import com.aldo.apps.ochecompanion.R;
|
||||
import com.aldo.apps.ochecompanion.database.AppDatabase;
|
||||
import com.aldo.apps.ochecompanion.database.DatabaseHelper;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||
import com.aldo.apps.ochecompanion.ui.adapter.PlayerSelectionAdapter;
|
||||
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
||||
@@ -27,19 +28,25 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* PlayerSelectionDialogFragment: A modern bottom-sheet selector for match participants.
|
||||
* PlayerSelectionDialogFragment: A modern bottom-sheet selector for match
|
||||
* participants.
|
||||
* <p>
|
||||
* This {@link BottomSheetDialogFragment} provides a user interface for selecting players
|
||||
* This {@link BottomSheetDialogFragment} provides a user interface for
|
||||
* selecting players
|
||||
* from the database before starting a new match. It features:
|
||||
* <ul>
|
||||
* <li>Automatic pre-selection of players from the most recent match for speed</li>
|
||||
* <li>Dynamic button state that displays the current selection count</li>
|
||||
* <li>Integration with {@link PlayerSelectionAdapter} for multi-select functionality</li>
|
||||
* <li>Validation to ensure at least one player is selected before starting</li>
|
||||
* <li>Automatic pre-selection of players from the most recent match for
|
||||
* speed</li>
|
||||
* <li>Dynamic button state that displays the current selection count</li>
|
||||
* <li>Integration with {@link PlayerSelectionAdapter} for multi-select
|
||||
* functionality</li>
|
||||
* <li>Validation to ensure at least one player is selected before starting</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The dialog automatically loads all players from the database on creation and queries
|
||||
* the last match's participant data to pre-populate selections, improving user experience
|
||||
* The dialog automatically loads all players from the database on creation and
|
||||
* queries
|
||||
* the last match's participant data to pre-populate selections, improving user
|
||||
* experience
|
||||
* for consecutive matches with the same players.
|
||||
*
|
||||
* @see PlayerSelectionAdapter
|
||||
@@ -86,50 +93,65 @@ public class PlayerSelectionDialogFragment extends BottomSheetDialogFragment {
|
||||
private MaterialButton mBtnStart;
|
||||
|
||||
/**
|
||||
* The adapter that manages the player selection list in the {@link RecyclerView}.
|
||||
* The adapter that manages the player selection list in the
|
||||
* {@link RecyclerView}.
|
||||
* <p>
|
||||
* Handles player selection logic, visual feedback, and communicates
|
||||
* selection changes via {@link #onSelectionChanged()}.
|
||||
*/
|
||||
private PlayerSelectionAdapter mAdapter;
|
||||
|
||||
/**
|
||||
* The database helper instance used for database operations.
|
||||
*/
|
||||
private DatabaseHelper mDatabaseHelper;
|
||||
|
||||
/**
|
||||
* Creates and returns the view hierarchy associated with this fragment.
|
||||
* <p>
|
||||
* Inflates the player selection bottom sheet layout, which includes:
|
||||
* <ul>
|
||||
* <li>A {@link RecyclerView} for displaying the player list</li>
|
||||
* <li>A {@link MaterialButton} for confirming the selection</li>
|
||||
* <li>A {@link RecyclerView} for displaying the player list</li>
|
||||
* <li>A {@link MaterialButton} for confirming the selection</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param inflater The {@link LayoutInflater} used to inflate views in the fragment
|
||||
* @param container The parent view that the fragment's UI should be attached to,
|
||||
* @param inflater The {@link LayoutInflater} used to inflate views in
|
||||
* the fragment
|
||||
* @param container The parent view that the fragment's UI should be
|
||||
* attached to,
|
||||
* or {@code null} if not attached
|
||||
* @param savedInstanceState If non-null, this fragment is being re-constructed from a
|
||||
* @param savedInstanceState If non-null, this fragment is being re-constructed
|
||||
* from a
|
||||
* previous saved state
|
||||
* @return The root {@link View} of the inflated layout
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
mDatabaseHelper = DatabaseHelper.getInstance(getContext());
|
||||
return inflater.inflate(R.layout.layout_player_selection_sheet, container, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called immediately after {@link #onCreateView} has returned, but before any saved
|
||||
* Called immediately after {@link #onCreateView} has returned, but before any
|
||||
* saved
|
||||
* state has been restored into the view.
|
||||
* <p>
|
||||
* This method performs the following initialization steps:
|
||||
* <ol>
|
||||
* <li>Binds UI components ({@link RecyclerView}, {@link MaterialButton})</li>
|
||||
* <li>Configures the {@link RecyclerView} with a {@link LinearLayoutManager}</li>
|
||||
* <li>Initializes the {@link PlayerSelectionAdapter} with selection callback</li>
|
||||
* <li>Sets up the confirmation button click listener</li>
|
||||
* <li>Triggers asynchronous data loading via {@link #loadData()}</li>
|
||||
* <li>Binds UI components ({@link RecyclerView}, {@link MaterialButton})</li>
|
||||
* <li>Configures the {@link RecyclerView} with a
|
||||
* {@link LinearLayoutManager}</li>
|
||||
* <li>Initializes the {@link PlayerSelectionAdapter} with selection
|
||||
* callback</li>
|
||||
* <li>Sets up the confirmation button click listener</li>
|
||||
* <li>Triggers asynchronous data loading via {@link #loadData()}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param view The {@link View} returned by {@link #onCreateView}
|
||||
* @param savedInstanceState If non-null, this fragment is being re-constructed from a
|
||||
* @param savedInstanceState If non-null, this fragment is being re-constructed
|
||||
* from a
|
||||
* previous saved state
|
||||
*/
|
||||
@Override
|
||||
@@ -154,12 +176,12 @@ public class PlayerSelectionDialogFragment extends BottomSheetDialogFragment {
|
||||
* This method performs database operations on a background thread to avoid
|
||||
* blocking the UI. The loading process includes:
|
||||
* <ol>
|
||||
* <li>Fetching all players from the database</li>
|
||||
* <li>Retrieving the last match's participant data for pre-selection</li>
|
||||
* <li>Parsing the last match data using {@link MatchProgressConverter}</li>
|
||||
* <li>Extracting player IDs from the previous match</li>
|
||||
* <li>Updating the UI on the main thread with loaded data</li>
|
||||
* <li>Pre-selecting players who participated in the last match</li>
|
||||
* <li>Fetching all players from the database</li>
|
||||
* <li>Retrieving the last match's participant data for pre-selection</li>
|
||||
* <li>Parsing the last match data using {@link MatchProgressConverter}</li>
|
||||
* <li>Extracting player IDs from the previous match</li>
|
||||
* <li>Updating the UI on the main thread with loaded data</li>
|
||||
* <li>Pre-selecting players who participated in the last match</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* This pre-selection behavior significantly improves user experience when
|
||||
@@ -167,13 +189,11 @@ public class PlayerSelectionDialogFragment extends BottomSheetDialogFragment {
|
||||
*/
|
||||
private void loadData() {
|
||||
new Thread(() -> {
|
||||
final AppDatabase db = AppDatabase.getDatabase(requireContext());
|
||||
|
||||
// 1. Get All Players
|
||||
final List<Player> players = db.playerDao().getAllPlayers();
|
||||
final List<Player> players = mDatabaseHelper.getAllPlayers();
|
||||
|
||||
// 2. Get Last Participants for Pre-selection
|
||||
final String lastJson = db.matchDao().getLastMatchParticipantData();
|
||||
final String lastJson = mDatabaseHelper.getLastMatchParticipantData();
|
||||
final MatchProgress lastProgress = MatchProgressConverter.fromString(lastJson);
|
||||
|
||||
final Set<Integer> lastPlayerIds = new HashSet<>();
|
||||
@@ -204,7 +224,8 @@ public class PlayerSelectionDialogFragment extends BottomSheetDialogFragment {
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method invoked by {@link PlayerSelectionAdapter} when the selection state changes.
|
||||
* Callback method invoked by {@link PlayerSelectionAdapter} when the selection
|
||||
* state changes.
|
||||
* <p>
|
||||
* This method is triggered whenever a player is selected or deselected in the
|
||||
* {@link RecyclerView}. It delegates to {@link #updateButtonState()} to reflect
|
||||
@@ -222,10 +243,12 @@ public class PlayerSelectionDialogFragment extends BottomSheetDialogFragment {
|
||||
* <p>
|
||||
* This method performs two UI updates:
|
||||
* <ul>
|
||||
* <li><b>Enabled State:</b> The button is enabled only when at least one player
|
||||
* is selected (count > 0), preventing match initiation with empty selections</li>
|
||||
* <li><b>Text Display:</b> Shows "START MATCH (count)" when players are selected,
|
||||
* or "SELECT PLAYERS" as a prompt when no selections are made</li>
|
||||
* <li><b>Enabled State:</b> The button is enabled only when at least one player
|
||||
* is selected (count > 0), preventing match initiation with empty
|
||||
* selections</li>
|
||||
* <li><b>Text Display:</b> Shows "START MATCH (count)" when players are
|
||||
* selected,
|
||||
* or "SELECT PLAYERS" as a prompt when no selections are made</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This provides clear visual feedback about the current selection state and
|
||||
@@ -242,12 +265,13 @@ public class PlayerSelectionDialogFragment extends BottomSheetDialogFragment {
|
||||
* <p>
|
||||
* This method performs the following operations:
|
||||
* <ol>
|
||||
* <li>Constructs a list of selected {@link Player} objects by filtering
|
||||
* {@link #mAllPlayers} based on {@link #mSelectedIds}</li>
|
||||
* <li>Validates that at least one player is selected (early return if empty)</li>
|
||||
* <li>Launches {@link GameActivity} with the selected players and default score
|
||||
* ({@link DartsConstants#DEFAULT_GAME_SCORE})</li>
|
||||
* <li>Dismisses this dialog fragment after successfully starting the match</li>
|
||||
* <li>Constructs a list of selected {@link Player} objects by filtering
|
||||
* {@link #mAllPlayers} based on {@link #mSelectedIds}</li>
|
||||
* <li>Validates that at least one player is selected (early return if
|
||||
* empty)</li>
|
||||
* <li>Launches {@link GameActivity} with the selected players and default score
|
||||
* ({@link DartsConstants#DEFAULT_GAME_SCORE})</li>
|
||||
* <li>Dismisses this dialog fragment after successfully starting the match</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* This method is triggered by the confirmation button click listener set up
|
||||
@@ -261,7 +285,8 @@ public class PlayerSelectionDialogFragment extends BottomSheetDialogFragment {
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedList.isEmpty()) return;
|
||||
if (selectedList.isEmpty())
|
||||
return;
|
||||
|
||||
// Start the game!
|
||||
GameActivity.start(requireContext(), selectedList, DartsConstants.DEFAULT_GAME_SCORE);
|
||||
|
||||
Reference in New Issue
Block a user