Refactored GameActivity
Refactored GameActivity to make use of the newly introduced GameManager to centralize the Game logic into one class and shrink the GameActivity to the relevant parts.
This commit is contained in:
@@ -27,10 +27,6 @@
|
||||
<activity android:name=".BaseActivity"
|
||||
android:exported="false"
|
||||
android:configChanges="uiMode"/>
|
||||
<activity
|
||||
android:name=".TestActivity"
|
||||
android:exported="false"
|
||||
android:configChanges="uiMode" />
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:exported="false"
|
||||
|
||||
@@ -18,11 +18,12 @@ import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -211,6 +212,13 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
// Set up touch gesture handlers for image cropping
|
||||
setupGestures();
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
handleBackPressed();
|
||||
}
|
||||
});
|
||||
|
||||
// Check if editing an existing player
|
||||
if (getIntent().hasExtra(EXTRA_PLAYER_ID)) {
|
||||
mExistingPlayerId = getIntent().getLongExtra(EXTRA_PLAYER_ID, -1);
|
||||
@@ -218,15 +226,13 @@ public class AddPlayerActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
Log.d(TAG, "onBackPressed() called with StatsView shown = [" + mIsStatsViewShown + "]");
|
||||
private void handleBackPressed() {
|
||||
if (mIsStatsViewShown) {
|
||||
mPlayerStatsView.setVisibility(View.GONE);
|
||||
mIsStatsViewShown = false;
|
||||
return;
|
||||
}
|
||||
super.onBackPressed();
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import com.aldo.apps.ochecompanion.utils.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.preference.PreferenceManager;
|
||||
@@ -64,7 +65,7 @@ public abstract class BaseActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(final Configuration newConfig) {
|
||||
public void onConfigurationChanged(final @NonNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
Log.d(TAG, "========================================");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import com.aldo.apps.ochecompanion.utils.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
@@ -15,7 +14,6 @@ import androidx.preference.PreferenceManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
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.database.objects.Match;
|
||||
@@ -25,7 +23,6 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -93,7 +90,7 @@ public class MainMenuActivity extends BaseActivity {
|
||||
quickStartBtn.setOnClickListener(v -> quickStart());
|
||||
findViewById(R.id.btnSettings).setOnClickListener(v -> launchSettings());
|
||||
|
||||
final List<Match> ongoingMatches = (List<Match>) mDatabaseHelper.getOngoingMatches();
|
||||
final List<Match> ongoingMatches = mDatabaseHelper.getOngoingMatches();
|
||||
if (ongoingMatches != null && !ongoingMatches.isEmpty()) {
|
||||
mOngoingMatch = ongoingMatches.get(0);
|
||||
}
|
||||
@@ -110,8 +107,6 @@ public class MainMenuActivity extends BaseActivity {
|
||||
mTestCounter++;
|
||||
new Thread(() -> mDatabaseHelper.printAllMatches()).start();
|
||||
});
|
||||
|
||||
findViewById(R.id.title_view).setOnClickListener(v -> startActivity(new Intent(MainMenuActivity.this, TestActivity.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +173,7 @@ public class MainMenuActivity extends BaseActivity {
|
||||
startActivity(intent);
|
||||
});
|
||||
new Thread(() -> {
|
||||
final List<Player> allPlayers = (List<Player>) mDatabaseHelper.getAllPlayers();
|
||||
final List<Player> allPlayers = mDatabaseHelper.getAllPlayers();
|
||||
runOnUiThread(() -> adapter.updatePlayers(allPlayers));
|
||||
|
||||
}).start();
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.aldo.apps.ochecompanion;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.aldo.apps.ochecompanion;
|
||||
|
||||
import android.os.Bundle;
|
||||
import com.aldo.apps.ochecompanion.utils.Log;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.aldo.apps.ochecompanion.database.AppDatabase;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Statistics;
|
||||
import com.aldo.apps.ochecompanion.ui.HeatmapView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TestActivity extends BaseActivity {
|
||||
|
||||
private static final String TAG = "TestActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
EdgeToEdge.enable(this);
|
||||
setContentView(R.layout.activity_test);
|
||||
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;
|
||||
});
|
||||
|
||||
final HeatmapView heatmap = findViewById(R.id.heatmap);
|
||||
new Thread(() -> {
|
||||
// Access the singleton database and query all players
|
||||
final List<Player> allPlayers = AppDatabase.getDatabase(getApplicationContext())
|
||||
.playerDao()
|
||||
.getAllPlayers();
|
||||
|
||||
if (allPlayers == null || allPlayers.isEmpty()) {
|
||||
Log.d(TAG, "onCreate: Cannot continue");
|
||||
return;
|
||||
}
|
||||
|
||||
final Player firstPlayer = allPlayers.get(0);
|
||||
final Statistics stats = AppDatabase.getDatabase(this)
|
||||
.statisticsDao()
|
||||
.getStatisticsForPlayer(firstPlayer.id);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
Log.d(TAG, "onCreate: Applying stats [" + stats + "]");
|
||||
heatmap.setStats(stats);
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import com.aldo.apps.ochecompanion.utils.converters.HitDistributionConverter;
|
||||
* @see Player
|
||||
* @see Match
|
||||
*/
|
||||
@Database(entities = {Player.class, Match.class, Statistics.class}, version = 12, exportSchema = false)
|
||||
@Database(entities = {Player.class, Match.class, Statistics.class}, version = 14, exportSchema = false)
|
||||
@TypeConverters({HitDistributionConverter.class})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import com.aldo.apps.ochecompanion.utils.Log;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Match;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Statistics;
|
||||
import com.aldo.apps.ochecompanion.utils.MatchProgress;
|
||||
import com.aldo.apps.ochecompanion.utils.converters.MatchProgressConverter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -292,16 +294,9 @@ public class DatabaseHelper {
|
||||
*
|
||||
* @return List of all players, or empty list if none exist
|
||||
*/
|
||||
public List<?> getAllPlayers() {
|
||||
public List<Player> getAllPlayers() {
|
||||
try {
|
||||
return mExecutor.submit(() -> {
|
||||
try {
|
||||
return mDatabase.playerDao().getAllPlayers();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getAllPlayers: Failed to retrieve players", e);
|
||||
return new java.util.ArrayList<>();
|
||||
}
|
||||
}).get();
|
||||
return mExecutor.submit(() -> mDatabase.playerDao().getAllPlayers()).get();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getAllPlayers: Failed to submit task", e);
|
||||
return new java.util.ArrayList<>();
|
||||
@@ -309,7 +304,45 @@ public class DatabaseHelper {
|
||||
}
|
||||
|
||||
public long createNewMatch(final String gameMode, final List<Player> players) {
|
||||
final Match match = new Match(System.currentTimeMillis(), gameMode, players.size(), null, Match.MatchState.ONGOING);
|
||||
// Parse starting score from gameMode string
|
||||
int startingScore = 501; // Default
|
||||
try {
|
||||
startingScore = Integer.parseInt(gameMode);
|
||||
} 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) {
|
||||
initialProgress.players.add(new MatchProgress.PlayerStateSnapshot(
|
||||
player.id,
|
||||
player.username,
|
||||
startingScore, // Initial score equals starting score
|
||||
0 // No darts thrown yet
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// Create guest player if no players provided
|
||||
initialProgress.players.add(new MatchProgress.PlayerStateSnapshot(
|
||||
0L, // Guest has ID 0
|
||||
"GUEST",
|
||||
startingScore,
|
||||
0
|
||||
));
|
||||
}
|
||||
|
||||
// Convert to JSON
|
||||
final String participantData = MatchProgressConverter.fromProgress(initialProgress);
|
||||
|
||||
final Match match = new Match(System.currentTimeMillis(), gameMode,
|
||||
initialProgress.players.size(), participantData, Match.MatchState.ONGOING);
|
||||
try {
|
||||
return mExecutor.submit(() -> {
|
||||
try {
|
||||
@@ -340,16 +373,9 @@ public class DatabaseHelper {
|
||||
});
|
||||
}
|
||||
|
||||
public List<?> getOngoingMatches() {
|
||||
public List<Match> getOngoingMatches() {
|
||||
try {
|
||||
return mExecutor.submit(() -> {
|
||||
try {
|
||||
return mDatabase.matchDao().getOngoingMatches();
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "getOngoingMatch() failed");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}).get();
|
||||
return mExecutor.submit(() -> mDatabase.matchDao().getOngoingMatches()).get();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getOngoingMatch: Failed fetching ongoing matches");
|
||||
return new ArrayList<>();
|
||||
|
||||
@@ -5,7 +5,6 @@ import static androidx.room.OnConflictStrategy.REPLACE;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.aldo.apps.ochecompanion.database.objects;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
||||
import com.aldo.apps.ochecompanion.utils.MatchProgress;
|
||||
import com.aldo.apps.ochecompanion.utils.converters.MatchProgressConverter;
|
||||
|
||||
@@ -267,7 +265,7 @@ public class Match implements Serializable {
|
||||
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;
|
||||
final int score = (scores != null && scores.containsKey((int) player.id)) ? scores.get((int) player.id) : 0;
|
||||
participant.put("score", score);
|
||||
participants.put(participant);
|
||||
}
|
||||
@@ -361,6 +359,7 @@ public class Match implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return "Match{" +
|
||||
"id=" + id +
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.aldo.apps.ochecompanion.utils.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
import androidx.room.TypeConverters;
|
||||
|
||||
import com.aldo.apps.ochecompanion.utils.converters.HitDistributionConverter;
|
||||
|
||||
|
||||
@@ -0,0 +1,699 @@
|
||||
package com.aldo.apps.ochecompanion.game;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.aldo.apps.ochecompanion.database.DatabaseHelper;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Match;
|
||||
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
||||
import com.aldo.apps.ochecompanion.utils.Log;
|
||||
import com.aldo.apps.ochecompanion.utils.MatchProgress;
|
||||
import com.aldo.apps.ochecompanion.utils.converters.MatchProgressConverter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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.
|
||||
* 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
|
||||
* that only handles UI updates via the GameStateCallback interface.
|
||||
*/
|
||||
public class GameManager {
|
||||
private static final String TAG = "GameManager";
|
||||
|
||||
// Singleton instance
|
||||
private static GameManager sInstance;
|
||||
|
||||
// Dependencies
|
||||
private final DatabaseHelper mDatabaseHelper;
|
||||
private GameStateCallback mCallback;
|
||||
|
||||
// Game State
|
||||
private int mMatchId = -1;
|
||||
private int mStartingScore = DartsConstants.DEFAULT_GAME_SCORE;
|
||||
private int mActivePlayerIndex = 0;
|
||||
private int mMultiplier = 1;
|
||||
private final List<PlayerState> mPlayerStates = new ArrayList<>();
|
||||
private final List<Integer> mCurrentTurnDarts = new ArrayList<>();
|
||||
private final List<DartHit> mCurrentTurnDartHits = new ArrayList<>();
|
||||
private boolean mIsTurnOver = false;
|
||||
private boolean mIsBustedTurn = false;
|
||||
private boolean mIsMatchCompleted = false;
|
||||
|
||||
/**
|
||||
* Callback interface for communicating game state changes to the UI layer.
|
||||
*/
|
||||
public interface GameStateCallback {
|
||||
/**
|
||||
* 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 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.
|
||||
*/
|
||||
public static class DartHit {
|
||||
public final int baseValue;
|
||||
public final int multiplier;
|
||||
|
||||
public DartHit(final int baseValue, final int multiplier) {
|
||||
this.baseValue = baseValue;
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State holder for a single player's X01 game progress.
|
||||
*/
|
||||
public static class PlayerState {
|
||||
public final Player player;
|
||||
public final long playerId;
|
||||
public final String name;
|
||||
public int remainingScore;
|
||||
public int dartsThrown = 0;
|
||||
|
||||
public PlayerState(final Player player, final int startScore) {
|
||||
this.player = player;
|
||||
this.playerId = player.id;
|
||||
this.name = player.username;
|
||||
this.remainingScore = startScore;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to enforce singleton pattern.
|
||||
*/
|
||||
private GameManager(final Context context) {
|
||||
mDatabaseHelper = DatabaseHelper.getInstance(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the singleton instance of GameManager.
|
||||
*
|
||||
* @param context Application or Activity context
|
||||
* @return The singleton GameManager instance
|
||||
*/
|
||||
public static synchronized GameManager getInstance(final Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new GameManager(context.getApplicationContext());
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to receive game state updates.
|
||||
*
|
||||
* @param callback The callback to register
|
||||
*/
|
||||
public void setCallback(final GameStateCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 startingScore The starting score (501, 301, etc.)
|
||||
* @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));
|
||||
|
||||
Match match = null;
|
||||
if (matchId > 0) {
|
||||
// Try to load existing match
|
||||
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");
|
||||
// Initialize player states
|
||||
initializePlayerStates(allPlayers);
|
||||
loadMatchProgress(progress);
|
||||
|
||||
if (onComplete != null) {
|
||||
onComplete.run();
|
||||
}
|
||||
notifyGameStateChanged();
|
||||
return;
|
||||
} else {
|
||||
Log.w(TAG, "initializeMatch: Progress is null, treating as new match");
|
||||
match = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "initializeMatch: Failed to load match progress", e);
|
||||
match = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create new match if not found or invalid
|
||||
if (match == null) {
|
||||
final long newMatchId = mDatabaseHelper.createNewMatch(String.valueOf(startingScore), allPlayers);
|
||||
if (newMatchId > 0) {
|
||||
mMatchId = (int) newMatchId;
|
||||
Log.d(TAG, "initializeMatch: Created new match with ID: " + mMatchId);
|
||||
} else {
|
||||
Log.e(TAG, "initializeMatch: Failed to create new match");
|
||||
}
|
||||
|
||||
// Setup new game
|
||||
initializePlayerStates(allPlayers);
|
||||
}
|
||||
|
||||
if (onComplete != null) {
|
||||
onComplete.run();
|
||||
}
|
||||
notifyGameStateChanged();
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes player states from the provided player list.
|
||||
*/
|
||||
private void initializePlayerStates(final List<Player> players) {
|
||||
mPlayerStates.clear();
|
||||
if (players != null && !players.isEmpty()) {
|
||||
for (Player p : players) {
|
||||
mPlayerStates.add(new PlayerState(p, mStartingScore));
|
||||
}
|
||||
} else {
|
||||
// Create guest player if no players available
|
||||
final Player guest = new Player("GUEST", null);
|
||||
mPlayerStates.add(new PlayerState(guest, mStartingScore));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads match progress from a saved state.
|
||||
*/
|
||||
private void loadMatchProgress(final MatchProgress progress) {
|
||||
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);
|
||||
PlayerState state = mPlayerStates.get(i);
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
int scoreAfterDart = scoreBeforeDart - points;
|
||||
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.
|
||||
*/
|
||||
private void handleWin(final PlayerState winner) {
|
||||
final int dartsThrown = mCurrentTurnDarts.size();
|
||||
int pointsMade = 0;
|
||||
for (int d : mCurrentTurnDarts) pointsMade += d;
|
||||
final int checkoutValue = mCurrentTurnDarts.get(mCurrentTurnDarts.size() - 1);
|
||||
|
||||
// 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;
|
||||
|
||||
// Calculate turn total
|
||||
int turnTotal = 0;
|
||||
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.
|
||||
*/
|
||||
public void undoLastDart() {
|
||||
if (!mCurrentTurnDarts.isEmpty()) {
|
||||
mCurrentTurnDarts.remove(mCurrentTurnDarts.size() - 1);
|
||||
mCurrentTurnDartHits.remove(mCurrentTurnDartHits.size() - 1);
|
||||
|
||||
mIsTurnOver = false;
|
||||
mIsBustedTurn = false;
|
||||
|
||||
notifyResetVisuals();
|
||||
notifyTurnIndicatorsChanged();
|
||||
notifyGameStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current multiplier.
|
||||
*
|
||||
* @param multiplier The multiplier value (1=Single, 2=Double, 3=Triple)
|
||||
*/
|
||||
public void setMultiplier(final int multiplier) {
|
||||
mMultiplier = multiplier;
|
||||
if (mCallback != null) {
|
||||
mCallback.onMultiplierChanged(multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current match progress to the database.
|
||||
*/
|
||||
private void saveMatchProgress() {
|
||||
final MatchProgress progress = new MatchProgress();
|
||||
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
|
||||
));
|
||||
}
|
||||
|
||||
String progressJson = MatchProgressConverter.fromProgress(progress);
|
||||
|
||||
if (mMatchId > 0) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Match match = mDatabaseHelper.getMatchById(mMatchId);
|
||||
if (match != null) {
|
||||
match.participantData = progressJson;
|
||||
match.timestamp = System.currentTimeMillis();
|
||||
mDatabaseHelper.updateMatch(match);
|
||||
Log.d(TAG, "saveMatchProgress: Saved match progress for match ID: " + mMatchId);
|
||||
} else {
|
||||
Log.e(TAG, "saveMatchProgress: Match not found with ID: " + mMatchId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "saveMatchProgress: Failed to save progress", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the completed match to the database.
|
||||
*/
|
||||
private void saveCompletedMatch(final PlayerState winner) {
|
||||
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
|
||||
));
|
||||
}
|
||||
|
||||
match.participantData = MatchProgressConverter.fromProgress(finalProgress);
|
||||
mDatabaseHelper.updateMatch(match);
|
||||
|
||||
Log.d(TAG, "saveCompletedMatch: Match " + mMatchId + " marked as completed");
|
||||
} else {
|
||||
Log.e(TAG, "saveCompletedMatch: Match not found with ID: " + mMatchId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "saveCompletedMatch: Failed to save completed match", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates player statistics in the database.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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,
|
||||
dartsThrown,
|
||||
pointsMade,
|
||||
wasBust,
|
||||
checkoutValue,
|
||||
active.dartsThrown
|
||||
)).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks a double-out attempt in player statistics.
|
||||
*/
|
||||
private void trackDoubleAttempt(final PlayerState playerState, final boolean isMissed) {
|
||||
new Thread(() -> mDatabaseHelper.trackDoubleAttempt(playerState.playerId, isMissed)).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments matches played counter for all players.
|
||||
*/
|
||||
private void incrementMatchesPlayed() {
|
||||
final List<Long> playerIds = new ArrayList<>();
|
||||
for (PlayerState playerState : mPlayerStates) {
|
||||
playerIds.add(playerState.playerId);
|
||||
}
|
||||
new Thread(() -> mDatabaseHelper.incrementMatchesPlayed(playerIds)).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Records dart hits to player statistics.
|
||||
*/
|
||||
private void recordTurnHitsToStatistics(final PlayerState playerState, final List<DartHit> dartHits) {
|
||||
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.
|
||||
*/
|
||||
public void resetGame() {
|
||||
mMatchId = -1;
|
||||
mActivePlayerIndex = 0;
|
||||
mMultiplier = 1;
|
||||
mPlayerStates.clear();
|
||||
mCurrentTurnDarts.clear();
|
||||
mCurrentTurnDartHits.clear();
|
||||
mIsTurnOver = false;
|
||||
mIsBustedTurn = false;
|
||||
mIsMatchCompleted = false;
|
||||
}
|
||||
|
||||
// ========================================================================================
|
||||
// Getters for Game State
|
||||
// ========================================================================================
|
||||
|
||||
public int getMatchId() {
|
||||
return mMatchId;
|
||||
}
|
||||
|
||||
public int getStartingScore() {
|
||||
return mStartingScore;
|
||||
}
|
||||
|
||||
public int getActivePlayerIndex() {
|
||||
return mActivePlayerIndex;
|
||||
}
|
||||
|
||||
public int getMultiplier() {
|
||||
return mMultiplier;
|
||||
}
|
||||
|
||||
public List<PlayerState> getPlayerStates() {
|
||||
return new ArrayList<>(mPlayerStates);
|
||||
}
|
||||
|
||||
public PlayerState getActivePlayer() {
|
||||
if (mPlayerStates.isEmpty()) return null;
|
||||
return mPlayerStates.get(mActivePlayerIndex);
|
||||
}
|
||||
|
||||
public List<Integer> getCurrentTurnDarts() {
|
||||
return new ArrayList<>(mCurrentTurnDarts);
|
||||
}
|
||||
|
||||
public List<DartHit> getCurrentTurnDartHits() {
|
||||
return new ArrayList<>(mCurrentTurnDartHits);
|
||||
}
|
||||
|
||||
public boolean isTurnOver() {
|
||||
return mIsTurnOver;
|
||||
}
|
||||
|
||||
public boolean isBustedTurn() {
|
||||
return mIsBustedTurn;
|
||||
}
|
||||
|
||||
public boolean isMatchCompleted() {
|
||||
return mIsMatchCompleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the current target score (remaining - current turn darts).
|
||||
* If turn is busted, returns the remaining score without subtracting bust darts.
|
||||
*/
|
||||
public int getCurrentTarget() {
|
||||
PlayerState active = getActivePlayer();
|
||||
if (active == null) return 0;
|
||||
|
||||
if (mIsBustedTurn) {
|
||||
return active.remainingScore;
|
||||
}
|
||||
|
||||
int turnPointsSoFar = 0;
|
||||
for (int d : mCurrentTurnDarts) turnPointsSoFar += d;
|
||||
return active.remainingScore - turnPointsSoFar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of darts remaining in the current turn.
|
||||
*/
|
||||
public int getDartsRemainingInTurn() {
|
||||
return 3 - mCurrentTurnDarts.size();
|
||||
}
|
||||
|
||||
// ========================================================================================
|
||||
// Callback Notification Methods
|
||||
// ========================================================================================
|
||||
|
||||
private void notifyGameStateChanged() {
|
||||
if (mCallback != null) {
|
||||
mCallback.onGameStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyTurnIndicatorsChanged() {
|
||||
if (mCallback != null) {
|
||||
mCallback.onTurnIndicatorsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyBust() {
|
||||
if (mCallback != null) {
|
||||
mCallback.onBust();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOneEighty() {
|
||||
if (mCallback != null) {
|
||||
mCallback.onOneEightyScored();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyResetVisuals() {
|
||||
if (mCallback != null) {
|
||||
mCallback.onResetVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,4 +65,20 @@ public class PlayerItemView extends MaterialCardView {
|
||||
mIvAvatar.setImageResource(R.drawable.ic_users);
|
||||
}
|
||||
}
|
||||
|
||||
public void bindWithScore(@NonNull final Player player, final int score) {
|
||||
|
||||
mTvUsername.setText(player.username);
|
||||
// Display match score instead of career average
|
||||
mTvStats.setText(String.valueOf(score));
|
||||
|
||||
if (player.profilePictureUri != null) {
|
||||
Glide.with(getContext())
|
||||
.load(player.profilePictureUri)
|
||||
.into(mIvAvatar);
|
||||
} else {
|
||||
mIvAvatar.setImageResource(R.drawable.ic_users);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.aldo.apps.ochecompanion.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
@@ -19,6 +20,8 @@ import com.google.android.material.imageview.ShapeableImageView;
|
||||
*/
|
||||
public class PlayerStatsView extends ScrollView {
|
||||
|
||||
private static final String TAG = "PlayerStatsView";
|
||||
|
||||
// UI References
|
||||
private HeatmapView mHeatmap;
|
||||
private ShapeableImageView mIvAvatar;
|
||||
@@ -55,6 +58,10 @@ public class PlayerStatsView extends ScrollView {
|
||||
* Binds both the player identity and their accumulated stats to the UI.
|
||||
*/
|
||||
public void bind(@NonNull final Player player, final @NonNull Statistics stats) {
|
||||
if (player == null || stats == null) {
|
||||
Log.e(TAG, "bind: Cannot bind, return");
|
||||
return;
|
||||
}
|
||||
// 1. Identity
|
||||
mTvUsername.setText(player.username.toUpperCase());
|
||||
if (player.profilePictureUri != null) {
|
||||
|
||||
@@ -113,27 +113,19 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
*/
|
||||
public static class GroupMatchHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
/** TextView displaying the player's name. */
|
||||
private final TextView mPlayerNameView;
|
||||
|
||||
/** TextView displaying the player's career average. */
|
||||
private final TextView mPlayerScoreView;
|
||||
|
||||
/** ShapeableImageView displaying the player's profile picture. */
|
||||
private final ShapeableImageView mPlayerImageView;
|
||||
/**
|
||||
* The underlying {@link PlayerItemView} to be populated.
|
||||
*/
|
||||
private final PlayerItemView mItemView;
|
||||
|
||||
/**
|
||||
* Constructs a new GroupMatchHolder and initializes child views.
|
||||
*
|
||||
* @param itemView The root view (PlayerItemView).
|
||||
*/
|
||||
public GroupMatchHolder(@NonNull final View itemView) {
|
||||
public GroupMatchHolder(@NonNull final PlayerItemView itemView) {
|
||||
super(itemView);
|
||||
|
||||
// Initialize references to child views
|
||||
mPlayerNameView = itemView.findViewById(R.id.tvPlayerName);
|
||||
mPlayerScoreView = itemView.findViewById(R.id.tvPlayerAvg);
|
||||
mPlayerImageView = itemView.findViewById(R.id.ivPlayerProfile);
|
||||
mItemView = itemView;
|
||||
|
||||
// Hide the chevron icon as group match items are not interactive
|
||||
itemView.findViewById(R.id.ivChevron).setVisibility(View.GONE);
|
||||
@@ -147,23 +139,7 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
||||
public void setParticipant(final Match.ParticipantData participantData) {
|
||||
final Player player = participantData.player;
|
||||
final int score = participantData.score;
|
||||
|
||||
// Set player name
|
||||
mPlayerNameView.setText(player.username);
|
||||
|
||||
// Display match score instead of career average
|
||||
mPlayerScoreView.setText(String.valueOf(score));
|
||||
|
||||
// Load profile picture or show default icon
|
||||
if (player.profilePictureUri != null) {
|
||||
// Use Glide to load image from URI with caching and memory management
|
||||
Glide.with(itemView.getContext())
|
||||
.load(player.profilePictureUri)
|
||||
.into(mPlayerImageView);
|
||||
} else {
|
||||
// No profile picture available - show default user icon
|
||||
mPlayerImageView.setImageResource(R.drawable.ic_users);
|
||||
}
|
||||
mItemView.bindWithScore(player, score);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,27 +111,19 @@ public class MainMenuPlayerAdapter extends RecyclerView.Adapter<MainMenuPlayerAd
|
||||
*/
|
||||
public static class PlayerCardHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
/** TextView displaying the player's name. */
|
||||
private final TextView mPlayerNameView;
|
||||
|
||||
/** TextView displaying the player's career average. */
|
||||
private final TextView mPlayerScoreView;
|
||||
|
||||
/** ShapeableImageView displaying the player's profile picture. */
|
||||
private final ShapeableImageView mPlayerImageView;
|
||||
/**
|
||||
* The underlying {@link PlayerItemView} to be populated.
|
||||
*/
|
||||
private final PlayerItemView mItemView;
|
||||
|
||||
/**
|
||||
* Constructs a new PlayerCardHolder and initializes child views.
|
||||
*
|
||||
* @param itemView The root view (PlayerItemView).
|
||||
*/
|
||||
public PlayerCardHolder(@NonNull final View itemView) {
|
||||
public PlayerCardHolder(@NonNull final PlayerItemView itemView) {
|
||||
super(itemView);
|
||||
|
||||
// Initialize references to child views
|
||||
mPlayerNameView = itemView.findViewById(R.id.tvPlayerName);
|
||||
mPlayerScoreView = itemView.findViewById(R.id.tvPlayerAvg);
|
||||
mPlayerImageView = itemView.findViewById(R.id.ivPlayerProfile);
|
||||
mItemView = itemView;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,25 +136,7 @@ public class MainMenuPlayerAdapter extends RecyclerView.Adapter<MainMenuPlayerAd
|
||||
|
||||
// Set up click listener to navigate to edit player screen
|
||||
itemView.setOnClickListener(v -> startEditPlayerActivity(itemView.getContext(), player));
|
||||
|
||||
// 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));
|
||||
|
||||
// Load profile picture or show default icon
|
||||
if (player.profilePictureUri != null) {
|
||||
// Use Glide to load image from URI with caching and memory management
|
||||
Glide.with(itemView.getContext())
|
||||
.load(player.profilePictureUri)
|
||||
.into(mPlayerImageView);
|
||||
} else {
|
||||
// No profile picture available - show default user icon
|
||||
mPlayerImageView.setImageResource(R.drawable.ic_users);
|
||||
}
|
||||
mItemView.bind(player);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.aldo.apps.ochecompanion.utils;
|
||||
|
||||
/**
|
||||
* Class {@link Log} is a wrapper class around android.util.Log.
|
||||
*
|
||||
* <p>
|
||||
* The sole purpose of this class is to have a single TAG by which all log output from the
|
||||
* CoreSyncService can later on be found in the log. The classes using this logging class may
|
||||
* still define their custom tag. This will ease identifying OcheCompanion logs.
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.content.Context;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.SoundPool;
|
||||
import android.os.Build;
|
||||
import com.aldo.apps.ochecompanion.utils.Log;
|
||||
|
||||
import com.aldo.apps.ochecompanion.R;
|
||||
|
||||
@@ -23,12 +22,6 @@ public final class SoundEngine {
|
||||
*/
|
||||
private static final String TAG = "SoundEngine";
|
||||
|
||||
/**
|
||||
* Application context used for audio operations.
|
||||
* On Android R+, uses attribution context for proper audio tracking.
|
||||
*/
|
||||
private final Context mContext;
|
||||
|
||||
/**
|
||||
* Singleton instance of the SoundEngine.
|
||||
*/
|
||||
@@ -71,12 +64,18 @@ public final class SoundEngine {
|
||||
* @param context Application context for loading sound resources
|
||||
*/
|
||||
private SoundEngine(final Context context) {
|
||||
Context contextToUse;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
mContext = context.createAttributionContext("oche_gameplay");
|
||||
contextToUse = context.createAttributionContext("oche_gameplay");
|
||||
} else {
|
||||
mContext = context;
|
||||
contextToUse = context;
|
||||
}
|
||||
mSoundPool = new SoundPool.Builder()
|
||||
final SoundPool.Builder soundPoolBuilder = new SoundPool.Builder();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
soundPoolBuilder.setContext(contextToUse);
|
||||
}
|
||||
mSoundPool = soundPoolBuilder
|
||||
.setMaxStreams(5)
|
||||
.setAudioAttributes(new AudioAttributes.Builder()
|
||||
.setUsage(USAGE_GAME)
|
||||
|
||||
@@ -58,7 +58,7 @@ public class HitDistributionConverter {
|
||||
return multiplier == 2 ? "db" : "sb";
|
||||
}
|
||||
|
||||
String prefix = "";
|
||||
String prefix;
|
||||
if (multiplier == 3) prefix = "t";
|
||||
else if (multiplier == 2) prefix = "d";
|
||||
else prefix = "s";
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".TestActivity">
|
||||
|
||||
<com.aldo.apps.ochecompanion.ui.HeatmapView
|
||||
android:id="@+id/heatmap"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="300dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Reference in New Issue
Block a user