Created a Player Selection
When a new Game is started now a Player Selection Screen pops up, to select the players to be performing in the next match.
This commit is contained in:
@@ -70,6 +70,11 @@ public class GameActivity extends BaseActivity implements GameManager.GameStateC
|
|||||||
*/
|
*/
|
||||||
private static final String EXTRA_MATCH_ID = "extra_match_uuid";
|
private static final String EXTRA_MATCH_ID = "extra_match_uuid";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intent extra for a player list. Making it possible to start a match with pre-defined players.
|
||||||
|
*/
|
||||||
|
private static final String EXTRA_PLAYERS = "extra_players";
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
// Game Manager (Singleton Business Logic Handler)
|
// Game Manager (Singleton Business Logic Handler)
|
||||||
@@ -196,9 +201,18 @@ public class GameActivity extends BaseActivity implements GameManager.GameStateC
|
|||||||
* @param matchId The ID of the match to be started/loaded.
|
* @param matchId The ID of the match to be started/loaded.
|
||||||
*/
|
*/
|
||||||
public static void start(final Context context, final int startScore, final int matchId) {
|
public static void start(final Context context, final int startScore, final int matchId) {
|
||||||
|
final GameManager gameManager = GameManager.getInstance(context);
|
||||||
|
gameManager.initializeMatch(matchId, startScore,null);
|
||||||
|
|
||||||
|
Intent intent = new Intent(context, GameActivity.class);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void start(final Context context, final List<Player> players, final int startScore) {
|
||||||
|
final GameManager gameManager = GameManager.getInstance(context);
|
||||||
|
gameManager.initializeMatch(-1, startScore, players,null);
|
||||||
|
|
||||||
Intent intent = new Intent(context, GameActivity.class);
|
Intent intent = new Intent(context, GameActivity.class);
|
||||||
intent.putExtra(EXTRA_START_SCORE, startScore);
|
|
||||||
intent.putExtra(EXTRA_MATCH_ID, matchId);
|
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,16 +237,12 @@ public class GameActivity extends BaseActivity implements GameManager.GameStateC
|
|||||||
|
|
||||||
// Initialize GameManager singleton
|
// Initialize GameManager singleton
|
||||||
mGameManager = GameManager.getInstance(this);
|
mGameManager = GameManager.getInstance(this);
|
||||||
mGameManager.setCallback(this);
|
|
||||||
|
|
||||||
// Extract game parameters from intent
|
|
||||||
final int startingScore = getIntent().getIntExtra(EXTRA_START_SCORE, DartsConstants.DEFAULT_GAME_SCORE);
|
|
||||||
final int matchId = getIntent().getIntExtra(EXTRA_MATCH_ID, -1);
|
|
||||||
|
|
||||||
// Initialize activity components in order
|
// Initialize activity components in order
|
||||||
initViews();
|
initViews();
|
||||||
setupKeyboard();
|
setupKeyboard();
|
||||||
|
|
||||||
|
mGameManager.setCallback(this);
|
||||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -240,13 +250,9 @@ public class GameActivity extends BaseActivity implements GameManager.GameStateC
|
|||||||
handleBackPressed();
|
handleBackPressed();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize match through GameManager (handles loading existing or creating new)
|
|
||||||
mGameManager.initializeMatch(matchId, startingScore, () -> runOnUiThread(() -> {
|
|
||||||
updateUI();
|
updateUI();
|
||||||
updateTurnIndicators();
|
updateTurnIndicators();
|
||||||
setMultiplier(DartsConstants.MULTIPLIER_SINGLE);
|
setMultiplier(DartsConstants.MULTIPLIER_SINGLE);
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.aldo.apps.ochecompanion;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.aldo.apps.ochecompanion.ui.PlayerSelectionDialogFragment;
|
||||||
import com.aldo.apps.ochecompanion.utils.Log;
|
import com.aldo.apps.ochecompanion.utils.Log;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@@ -145,10 +147,13 @@ public class MainMenuActivity extends BaseActivity {
|
|||||||
} catch (final NumberFormatException exception) {
|
} catch (final NumberFormatException exception) {
|
||||||
Log.e(TAG, "quickStart: Could not extract score from [" + mOngoingMatch + "], use default");
|
Log.e(TAG, "quickStart: Could not extract score from [" + mOngoingMatch + "], use default");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Log.d(TAG, "quickStart: Starting match with StartingScore = [" + startingScore
|
Log.d(TAG, "quickStart: Starting match with StartingScore = [" + startingScore
|
||||||
+ "] and matchId = [" + matchId + "]");
|
+ "] and matchId = [" + matchId + "]");
|
||||||
GameActivity.start(MainMenuActivity.this, startingScore, matchId);
|
GameActivity.start(MainMenuActivity.this, startingScore, matchId);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "quickStart: No ongoing matches found");
|
||||||
|
new PlayerSelectionDialogFragment().show(getSupportFragmentManager(), "squad_selector");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -88,4 +88,12 @@ public interface MatchDao {
|
|||||||
*/
|
*/
|
||||||
@Query("SELECT * FROM matches WHERE state = :state ORDER BY timestamp DESC")
|
@Query("SELECT * FROM matches WHERE state = :state ORDER BY timestamp DESC")
|
||||||
List<Match> getMatchesByState(final String state);
|
List<Match> getMatchesByState(final String state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves ONLY the participant JSON string from the most recent match.
|
||||||
|
* Used for the "Auto-select last players" feature in the Squad Selector.
|
||||||
|
*/
|
||||||
|
@Query("SELECT participantData FROM matches ORDER BY timestamp DESC LIMIT 1")
|
||||||
|
String getLastMatchParticipantData();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,24 +149,15 @@ public class GameManager {
|
|||||||
*/
|
*/
|
||||||
public void setCallback(final GameStateCallback callback) {
|
public void setCallback(final GameStateCallback callback) {
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
|
//Send one initial callback
|
||||||
|
notifyGameStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void initializeMatch(final int matchId,final int startingScore, final List<Player> players, final Runnable onComplete) {
|
||||||
* 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;
|
mStartingScore = startingScore;
|
||||||
mMatchId = matchId;
|
mMatchId = matchId;
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
final List<Player> allPlayers = mDatabaseHelper.getAllPlayers();
|
|
||||||
Log.d(TAG, "initializeMatch: Loading players, count = " + (allPlayers != null ? allPlayers.size() : 0));
|
|
||||||
|
|
||||||
Match match = null;
|
Match match = null;
|
||||||
if (matchId > 0) {
|
if (matchId > 0) {
|
||||||
// Try to load existing match
|
// Try to load existing match
|
||||||
@@ -181,7 +172,7 @@ public class GameManager {
|
|||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
Log.d(TAG, "initializeMatch: Found saved progress with " + progress.players.size() + " players");
|
Log.d(TAG, "initializeMatch: Found saved progress with " + progress.players.size() + " players");
|
||||||
// Initialize player states
|
// Initialize player states
|
||||||
initializePlayerStates(allPlayers);
|
initializePlayerStates(players);
|
||||||
loadMatchProgress(progress);
|
loadMatchProgress(progress);
|
||||||
|
|
||||||
if (onComplete != null) {
|
if (onComplete != null) {
|
||||||
@@ -202,7 +193,7 @@ public class GameManager {
|
|||||||
|
|
||||||
// Create new match if not found or invalid
|
// Create new match if not found or invalid
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
final long newMatchId = mDatabaseHelper.createNewMatch(String.valueOf(startingScore), allPlayers);
|
final long newMatchId = mDatabaseHelper.createNewMatch(String.valueOf(startingScore), players);
|
||||||
if (newMatchId > 0) {
|
if (newMatchId > 0) {
|
||||||
mMatchId = (int) newMatchId;
|
mMatchId = (int) newMatchId;
|
||||||
Log.d(TAG, "initializeMatch: Created new match with ID: " + mMatchId);
|
Log.d(TAG, "initializeMatch: Created new match with ID: " + mMatchId);
|
||||||
@@ -211,7 +202,7 @@ public class GameManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup new game
|
// Setup new game
|
||||||
initializePlayerStates(allPlayers);
|
initializePlayerStates(players);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onComplete != null) {
|
if (onComplete != null) {
|
||||||
@@ -221,6 +212,26 @@ public class GameManager {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new game or loads an existing match from the database.
|
||||||
|
* This should be called when starting/resuming a match.
|
||||||
|
*
|
||||||
|
* @param matchId The match ID to load, or -1 to create a new match
|
||||||
|
* @param 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));
|
||||||
|
initializeMatch(matchId, startingScore, allPlayers, onComplete);
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes player states from the provided player list.
|
* Initializes player states from the provided player list.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.aldo.apps.ochecompanion.ui;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
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.objects.Player;
|
||||||
|
import com.aldo.apps.ochecompanion.ui.adapter.PlayerSelectionAdapter;
|
||||||
|
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
||||||
|
import com.aldo.apps.ochecompanion.utils.MatchProgress;
|
||||||
|
import com.aldo.apps.ochecompanion.utils.converters.MatchProgressConverter;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||||
|
import com.google.android.material.button.MaterialButton;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PlayerSelectionDialogFragment: A modern bottom-sheet selector for match participants.
|
||||||
|
* Automatically pre-selects players from the most recent match for speed.
|
||||||
|
*/
|
||||||
|
public class PlayerSelectionDialogFragment extends BottomSheetDialogFragment {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag for debugging and logging purposes.
|
||||||
|
*/
|
||||||
|
private static final String TAG = "PlayerSelectionDialogFragment";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link List} of the selected {@link Player} for the match.
|
||||||
|
*/
|
||||||
|
private final List<Player> mAllPlayers = new ArrayList<>();
|
||||||
|
private final Set<Integer> mSelectedIds = new HashSet<>();
|
||||||
|
|
||||||
|
private RecyclerView mRvSquad;
|
||||||
|
private MaterialButton mBtnStart;
|
||||||
|
private PlayerSelectionAdapter mAdapter;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.layout_player_selection_sheet, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
mRvSquad = view.findViewById(R.id.rvSquadSelection);
|
||||||
|
mBtnStart = view.findViewById(R.id.btnConfirmSelection);
|
||||||
|
|
||||||
|
mRvSquad.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
mAdapter = new PlayerSelectionAdapter(mAllPlayers, mSelectedIds, this::onSelectionChanged);
|
||||||
|
mRvSquad.setAdapter(mAdapter);
|
||||||
|
|
||||||
|
mBtnStart.setOnClickListener(v -> initiateMatch());
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadData() {
|
||||||
|
new Thread(() -> {
|
||||||
|
AppDatabase db = AppDatabase.getDatabase(requireContext());
|
||||||
|
|
||||||
|
// 1. Get All Players
|
||||||
|
List<Player> players = db.playerDao().getAllPlayers();
|
||||||
|
|
||||||
|
// 2. Get Last Participants for Pre-selection
|
||||||
|
String lastJson = db.matchDao().getLastMatchParticipantData();
|
||||||
|
MatchProgress lastProgress = MatchProgressConverter.fromString(lastJson);
|
||||||
|
|
||||||
|
final Set<Integer> lastPlayerIds = new HashSet<>();
|
||||||
|
if (lastProgress != null && lastProgress.players != null) {
|
||||||
|
for (MatchProgress.PlayerStateSnapshot p : lastProgress.players) {
|
||||||
|
lastPlayerIds.add((int) p.playerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> {
|
||||||
|
mAllPlayers.clear();
|
||||||
|
mAllPlayers.addAll(players);
|
||||||
|
|
||||||
|
// Auto-select players from the previous session
|
||||||
|
mSelectedIds.clear();
|
||||||
|
for (Player p : mAllPlayers) {
|
||||||
|
if (lastPlayerIds.contains((int) p.id)) {
|
||||||
|
mSelectedIds.add((int) p.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
updateButtonState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSelectionChanged() {
|
||||||
|
updateButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateButtonState() {
|
||||||
|
int count = mSelectedIds.size();
|
||||||
|
mBtnStart.setEnabled(count > 0);
|
||||||
|
mBtnStart.setText(count > 0 ? "START MATCH (" + count + ")" : "SELECT PLAYERS");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initiateMatch() {
|
||||||
|
ArrayList<Player> selectedList = new ArrayList<>();
|
||||||
|
for (Player p : mAllPlayers) {
|
||||||
|
if (mSelectedIds.contains((int) p.id)) {
|
||||||
|
selectedList.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedList.isEmpty()) return;
|
||||||
|
|
||||||
|
// Start the game!
|
||||||
|
GameActivity.start(requireContext(), selectedList, DartsConstants.DEFAULT_GAME_SCORE);
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.aldo.apps.ochecompanion.ui.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.aldo.apps.ochecompanion.R;
|
||||||
|
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.google.android.material.card.MaterialCardView;
|
||||||
|
import com.google.android.material.imageview.ShapeableImageView;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PlayerSelectionAdapter: Optimized for the "Squad Selector" Bottom Sheet.
|
||||||
|
* Features specialized selection visual states and haptic feedback.
|
||||||
|
*/
|
||||||
|
public class PlayerSelectionAdapter extends RecyclerView.Adapter<PlayerSelectionAdapter.SelectionHolder> {
|
||||||
|
|
||||||
|
private final List<Player> mPlayers;
|
||||||
|
private final Set<Integer> mSelectedIds;
|
||||||
|
private final Runnable mOnChanged;
|
||||||
|
|
||||||
|
public PlayerSelectionAdapter(List<Player> players, Set<Integer> selected, Runnable onChanged) {
|
||||||
|
this.mPlayers = players;
|
||||||
|
this.mSelectedIds = selected;
|
||||||
|
this.mOnChanged = onChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SelectionHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_player_selection, parent, false);
|
||||||
|
return new SelectionHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull SelectionHolder holder, int position) {
|
||||||
|
Player p = mPlayers.get(position);
|
||||||
|
holder.bind(p, mSelectedIds.contains((int) p.id));
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
// Haptic Feedback
|
||||||
|
Vibrator vib = (Vibrator) v.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
if (vib != null) vib.vibrate(20);
|
||||||
|
|
||||||
|
if (mSelectedIds.contains((int) p.id)) {
|
||||||
|
mSelectedIds.remove((int) p.id);
|
||||||
|
} else {
|
||||||
|
if (mSelectedIds.size() < 8) { // PDC/Standard limit
|
||||||
|
mSelectedIds.add((int) p.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifyItemChanged(position);
|
||||||
|
mOnChanged.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mPlayers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SelectionHolder extends RecyclerView.ViewHolder {
|
||||||
|
private final ShapeableImageView ivAvatar;
|
||||||
|
private final TextView tvName;
|
||||||
|
private final MaterialCardView card;
|
||||||
|
private final View selectionIndicator;
|
||||||
|
|
||||||
|
public SelectionHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ivAvatar = itemView.findViewById(R.id.ivPlayerProfile);
|
||||||
|
tvName = itemView.findViewById(R.id.tvPlayerName);
|
||||||
|
card = (MaterialCardView) itemView;
|
||||||
|
selectionIndicator = itemView.findViewById(R.id.selectionOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(Player p, boolean isSelected) {
|
||||||
|
tvName.setText(p.username);
|
||||||
|
|
||||||
|
if (p.profilePictureUri != null) {
|
||||||
|
Glide.with(itemView.getContext()).load(p.profilePictureUri).into(ivAvatar);
|
||||||
|
} else {
|
||||||
|
ivAvatar.setImageResource(R.drawable.ic_users);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visual toggle
|
||||||
|
if (isSelected) {
|
||||||
|
card.setStrokeColor(ContextCompat.getColor(itemView.getContext(), R.color.volt_green));
|
||||||
|
card.setStrokeWidth(4);
|
||||||
|
selectionIndicator.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
card.setStrokeColor(ContextCompat.getColor(itemView.getContext(), R.color.border_subtle));
|
||||||
|
card.setStrokeWidth(1);
|
||||||
|
selectionIndicator.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,31 @@
|
|||||||
package com.aldo.apps.ochecompanion.utils.converters;
|
package com.aldo.apps.ochecompanion.utils.converters;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.room.TypeConverter;
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
import com.aldo.apps.ochecompanion.utils.MatchProgress;
|
import com.aldo.apps.ochecompanion.utils.MatchProgress;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MatchProgressConverter: Handles serialization of complex match states
|
* MatchProgressConverter: Handles serialization of complex match states
|
||||||
* into JSON strings for storage in the Room 'matches' table.
|
* into JSON strings for storage in the Room 'matches' table.
|
||||||
*/
|
*/
|
||||||
public class MatchProgressConverter {
|
public class MatchProgressConverter {
|
||||||
|
|
||||||
|
private static final String TAG = "MatchProgressConverter";
|
||||||
private static final Gson gson = new Gson();
|
private static final Gson gson = new Gson();
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static MatchProgress fromString(String value) {
|
public static MatchProgress fromString(String value) {
|
||||||
if (value == null || value.isEmpty()) return null;
|
if (value == null || value.isEmpty()) return null;
|
||||||
|
try {
|
||||||
return gson.fromJson(value, MatchProgress.class);
|
return gson.fromJson(value, MatchProgress.class);
|
||||||
|
} catch (final JsonSyntaxException ex) {
|
||||||
|
Log.e(TAG, "fromString: Failed to parse json, return null", ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
|
|||||||
71
app/src/main/res/layout/item_player_selection.xml
Normal file
71
app/src/main/res/layout/item_player_selection.xml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView 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:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="6dp"
|
||||||
|
app:cardBackgroundColor="@color/surface_secondary"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="0dp"
|
||||||
|
app:strokeColor="@color/border_subtle"
|
||||||
|
app:strokeWidth="1dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/ivPlayerProfile"
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:shapeAppearanceOverlay="@style/ShapeAppearance.MaterialComponents.SmallComponent"
|
||||||
|
tools:src="@drawable/ic_users" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPlayerName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:fontFamily="sans-serif-black"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/selectionOverlay"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/ivPlayerProfile"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Snakebite" />
|
||||||
|
|
||||||
|
<!-- Checkmark/Selection Indicator -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/selectionOverlay"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/shape_circle_overlay"
|
||||||
|
android:backgroundTint="@color/volt_green" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_chevron_right"
|
||||||
|
app:tint="@color/midnight_black" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
62
app/src/main/res/layout/layout_player_selection_sheet.xml
Normal file
62
app/src/main/res/layout/layout_player_selection_sheet.xml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout 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:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/surface_primary"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<!-- Bottom Sheet Drag Handle -->
|
||||||
|
<View
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:background="@color/text_dim"
|
||||||
|
android:alpha="0.5" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="WHO'S ON THE OCHE?"
|
||||||
|
android:fontFamily="sans-serif-black"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:letterSpacing="0.05" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
android:text="Select up to 8 players for this match"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:letterSpacing="0.1" />
|
||||||
|
|
||||||
|
<!-- Squad List -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvSquadSelection"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="320dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="80dp"
|
||||||
|
tools:listitem="@layout/item_player_selection" />
|
||||||
|
|
||||||
|
<!-- Action Button (Positioned at bottom of sheet) -->
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnConfirmSelection"
|
||||||
|
style="@style/Widget.Oche_Button_Primary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_marginTop="-70dp"
|
||||||
|
android:text="START MATCH"
|
||||||
|
app:icon="@drawable/ic_play"
|
||||||
|
app:iconGravity="textEnd"
|
||||||
|
app:iconSize="20dp"
|
||||||
|
app:cornerRadius="16dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user