Added primaryPlayer functionality
Added a new functionality to mark one player as the primary player. This player is the main user of the app and the training data will be stored for that player only.
This commit is contained in:
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2026-01-26T12:37:47.296146997Z">
|
<DropdownSelection timestamp="2026-02-06T14:54:23.364337885Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=/home/aldo270717/.android/avd/Pixel_9_Pro_API_36.avd" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=/home/aldo270717/.android/avd/Pixel_9_Pro.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.aldo.apps.ochecompanion"
|
namespace = "com.aldo.apps.ochecompanion"
|
||||||
compileSdk {
|
compileSdk = 36
|
||||||
version = release(36)
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.aldo.apps.ochecompanion"
|
applicationId = "com.aldo.apps.ochecompanion"
|
||||||
|
|||||||
@@ -104,6 +104,16 @@ public class AddPlayerActivity extends BaseActivity {
|
|||||||
*/
|
*/
|
||||||
private ImageView mBtnDelete;
|
private ImageView mBtnDelete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to toggle primary user status.
|
||||||
|
*/
|
||||||
|
private com.google.android.material.switchmaterial.SwitchMaterial mPrimaryUserSwitch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon that gets tinted when primary user is selected.
|
||||||
|
*/
|
||||||
|
private ImageView mPrimaryUserIcon;
|
||||||
|
|
||||||
// ========== UI - Cropper Views ==========
|
// ========== UI - Cropper Views ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,6 +286,25 @@ public class AddPlayerActivity extends BaseActivity {
|
|||||||
mIvCropPreview = findViewById(R.id.ivCropPreview);
|
mIvCropPreview = findViewById(R.id.ivCropPreview);
|
||||||
mCropOverlay = findViewById(R.id.cropOverlay);
|
mCropOverlay = findViewById(R.id.cropOverlay);
|
||||||
|
|
||||||
|
// Get references to primary user toggle
|
||||||
|
mPrimaryUserSwitch = findViewById(R.id.switchPrimaryUser);
|
||||||
|
mPrimaryUserIcon = findViewById(R.id.ivPrimaryUserIcon);
|
||||||
|
|
||||||
|
// Set up primary user toggle listener to tint icon
|
||||||
|
mPrimaryUserSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||||
|
if (isChecked) {
|
||||||
|
// Tint icon with volt green when selected
|
||||||
|
mPrimaryUserIcon.setImageTintList(
|
||||||
|
android.content.res.ColorStateList.valueOf(
|
||||||
|
getColor(R.color.volt_green)));
|
||||||
|
} else {
|
||||||
|
// Reset to dim color when unselected
|
||||||
|
mPrimaryUserIcon.setImageTintList(
|
||||||
|
android.content.res.ColorStateList.valueOf(
|
||||||
|
getColor(R.color.text_dim)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Set up click listeners
|
// Set up click listeners
|
||||||
mProfilePictureView.setOnClickListener(v -> checkForPermissionAndLaunchImagePicker());
|
mProfilePictureView.setOnClickListener(v -> checkForPermissionAndLaunchImagePicker());
|
||||||
mSaveButton.setOnClickListener(v -> savePlayer());
|
mSaveButton.setOnClickListener(v -> savePlayer());
|
||||||
@@ -549,6 +578,9 @@ public class AddPlayerActivity extends BaseActivity {
|
|||||||
mProfilePictureView.setImageTintList(null); // Remove placeholder tint
|
mProfilePictureView.setImageTintList(null); // Remove placeholder tint
|
||||||
mProfilePictureView.setImageURI(Uri.fromFile(new File(mInternalImagePath)));
|
mProfilePictureView.setImageURI(Uri.fromFile(new File(mInternalImagePath)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set primary user toggle state
|
||||||
|
mPrimaryUserSwitch.setChecked(mExistingPlayer.isPrimaryUser);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).start();
|
}).start();
|
||||||
@@ -584,16 +616,26 @@ public class AddPlayerActivity extends BaseActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get primary user toggle state
|
||||||
|
final boolean isPrimaryUser = mPrimaryUserSwitch.isChecked();
|
||||||
|
|
||||||
// Perform database operation on background thread
|
// Perform database operation on background thread
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
|
// If setting as primary user, clear all other primary flags first
|
||||||
|
if (isPrimaryUser) {
|
||||||
|
mDatabaseHelper.clearAllPrimaryFlags();
|
||||||
|
}
|
||||||
|
|
||||||
if (mExistingPlayer != null) {
|
if (mExistingPlayer != null) {
|
||||||
// Update existing player
|
// Update existing player
|
||||||
mExistingPlayer.username = name;
|
mExistingPlayer.username = name;
|
||||||
mExistingPlayer.profilePictureUri = mInternalImagePath;
|
mExistingPlayer.profilePictureUri = mInternalImagePath;
|
||||||
|
mExistingPlayer.isPrimaryUser = isPrimaryUser;
|
||||||
mDatabaseHelper.updatePlayer(mExistingPlayer);
|
mDatabaseHelper.updatePlayer(mExistingPlayer);
|
||||||
} else {
|
} else {
|
||||||
// Create and insert new player
|
// Create and insert new player
|
||||||
final Player p = new Player(name, mInternalImagePath);
|
final Player p = new Player(name, mInternalImagePath);
|
||||||
|
p.isPrimaryUser = isPrimaryUser;
|
||||||
final long newUserId = mDatabaseHelper.insertPlayer(p);
|
final long newUserId = mDatabaseHelper.insertPlayer(p);
|
||||||
final Player dbPlayer = mDatabaseHelper.getPlayerById(newUserId);
|
final Player dbPlayer = mDatabaseHelper.getPlayerById(newUserId);
|
||||||
if (dbPlayer != null) {
|
if (dbPlayer != null) {
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import com.aldo.apps.ochecompanion.utils.converters.HitDistributionConverter;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Main Room database class for the Oche Companion darts application.
|
* Main Room database class for the Oche Companion darts application.
|
||||||
* Manages data persistence for players, matches, and statistics using the Singleton pattern.
|
* Manages data persistence for players, matches, and statistics using the
|
||||||
|
* Singleton pattern.
|
||||||
* Uses version 2 with destructive migration (data lost on schema changes).
|
* Uses version 2 with destructive migration (data lost on schema changes).
|
||||||
* Database operations must be performed on background threads.
|
* Database operations must be performed on background threads.
|
||||||
*
|
*
|
||||||
@@ -25,7 +26,7 @@ import com.aldo.apps.ochecompanion.utils.converters.HitDistributionConverter;
|
|||||||
* @see Player
|
* @see Player
|
||||||
* @see Match
|
* @see Match
|
||||||
*/
|
*/
|
||||||
@Database(entities = {Player.class, Match.class, Statistics.class}, version = 14, exportSchema = false)
|
@Database(entities = { Player.class, Match.class, Statistics.class }, version = 15, exportSchema = false)
|
||||||
@TypeConverters({ HitDistributionConverter.class })
|
@TypeConverters({ HitDistributionConverter.class })
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
|
|
||||||
@@ -64,9 +65,11 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the singleton AppDatabase instance, creating it if necessary.
|
* Gets the singleton AppDatabase instance, creating it if necessary.
|
||||||
* Thread-safe using double-checked locking. Uses application context to prevent leaks.
|
* Thread-safe using double-checked locking. Uses application context to prevent
|
||||||
|
* leaks.
|
||||||
*
|
*
|
||||||
* @param context Context used to create the database (converted to application context)
|
* @param context Context used to create the database (converted to application
|
||||||
|
* context)
|
||||||
* @return Singleton AppDatabase instance
|
* @return Singleton AppDatabase instance
|
||||||
*/
|
*/
|
||||||
public static AppDatabase getDatabase(final Context context) {
|
public static AppDatabase getDatabase(final Context context) {
|
||||||
@@ -91,5 +94,3 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||||||
return sInstance;
|
return sInstance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -577,6 +577,34 @@ public class DatabaseHelper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current primary user from the database.
|
||||||
|
*
|
||||||
|
* @return The primary user, or null if none is set
|
||||||
|
*/
|
||||||
|
public Player getPrimaryUser() {
|
||||||
|
try {
|
||||||
|
return mExecutor.submit(() -> mDatabase.playerDao().getPrimaryUser()).get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "getPrimaryUser: Failed to get primary user", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the primary user flag from all players.
|
||||||
|
* Should be called before setting a new primary user to ensure only one exists.
|
||||||
|
*/
|
||||||
|
public void clearAllPrimaryFlags() {
|
||||||
|
mExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
mDatabase.playerDao().clearAllPrimaryFlags();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "clearAllPrimaryFlags: Failed to clear flags", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a player by their database ID synchronously.
|
* Retrieves a player by their database ID synchronously.
|
||||||
* Blocks until the operation completes to ensure consistency with any pending
|
* Blocks until the operation completes to ensure consistency with any pending
|
||||||
|
|||||||
@@ -58,11 +58,29 @@ public interface PlayerDao {
|
|||||||
Player getPlayerById(final long id);
|
Player getPlayerById(final long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all players ordered alphabetically by username.
|
* Retrieves all players with primary user first, then alphabetically by
|
||||||
|
* username.
|
||||||
* Must be called on a background thread.
|
* Must be called on a background thread.
|
||||||
*
|
*
|
||||||
* @return List of all players sorted A-Z
|
* @return List of all players with primary user at top, rest sorted A-Z
|
||||||
*/
|
*/
|
||||||
@Query("SELECT * FROM players ORDER BY username ASC")
|
@Query("SELECT * FROM players ORDER BY isPrimaryUser DESC, username ASC")
|
||||||
List<Player> getAllPlayers();
|
List<Player> getAllPlayers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current primary user.
|
||||||
|
* Must be called on a background thread.
|
||||||
|
*
|
||||||
|
* @return The primary user, or null if none is set
|
||||||
|
*/
|
||||||
|
@Query("SELECT * FROM players WHERE isPrimaryUser = 1 LIMIT 1")
|
||||||
|
Player getPrimaryUser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the primary user flag from all players.
|
||||||
|
* Used before setting a new primary user to ensure only one exists.
|
||||||
|
* Must be called on a background thread.
|
||||||
|
*/
|
||||||
|
@Query("UPDATE players SET isPrimaryUser = 0")
|
||||||
|
void clearAllPrimaryFlags();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ public class Player implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
public int matchesPlayed = 0;
|
public int matchesPlayed = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating whether this player is the primary user or not.
|
||||||
|
*/
|
||||||
|
public boolean isPrimaryUser = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new Player ready for database insertion. ID is auto-generated
|
* Constructs a new Player ready for database insertion. ID is auto-generated
|
||||||
* by Room, statistical fields default to 0.
|
* by Room, statistical fields default to 0.
|
||||||
@@ -69,6 +74,7 @@ public class Player implements Parcelable {
|
|||||||
profilePictureUri = in.readString();
|
profilePictureUri = in.readString();
|
||||||
careerAverage = in.readDouble();
|
careerAverage = in.readDouble();
|
||||||
matchesPlayed = in.readInt();
|
matchesPlayed = in.readInt();
|
||||||
|
isPrimaryUser = in.readByte() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,6 +110,7 @@ public class Player implements Parcelable {
|
|||||||
dest.writeString(profilePictureUri);
|
dest.writeString(profilePictureUri);
|
||||||
dest.writeDouble(careerAverage);
|
dest.writeDouble(careerAverage);
|
||||||
dest.writeInt(matchesPlayed);
|
dest.writeInt(matchesPlayed);
|
||||||
|
dest.writeByte((byte) (isPrimaryUser ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,6 +128,7 @@ public class Player implements Parcelable {
|
|||||||
", mProfilePictureUri='" + profilePictureUri + '\'' +
|
", mProfilePictureUri='" + profilePictureUri + '\'' +
|
||||||
", mCareerAverage=" + careerAverage +
|
", mCareerAverage=" + careerAverage +
|
||||||
", mMatchesPlayed=" + matchesPlayed +
|
", mMatchesPlayed=" + matchesPlayed +
|
||||||
|
", isPrimaryUser=" + isPrimaryUser +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
app/src/main/res/drawable/ic_favorite_profile.xml
Normal file
14
app/src/main/res/drawable/ic_favorite_profile.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<!-- Outline version for unselected state -->
|
||||||
|
<path
|
||||||
|
android:strokeColor="@color/text_dim"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M5,16 L3,5 L8.5,10 L12,4 L15.5,10 L21,5 L19,16 L5,16 Z M5,18 L19,18" />
|
||||||
|
</vector>
|
||||||
@@ -61,6 +61,39 @@
|
|||||||
android:textColor="@color/text_primary" />
|
android:textColor="@color/text_primary" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Primary User Toggle -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="@color/surface_secondary"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivPrimaryUserIcon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_favorite_profile"
|
||||||
|
app:tint="@color/text_dim" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:text="@string/txt_primary_user_label"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/switchPrimaryUser"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="false" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnShowStats"
|
android:id="@+id/btnShowStats"
|
||||||
style="@style/Widget.Oche_Button_Primary"
|
style="@style/Widget.Oche_Button_Primary"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<string name="txt_create_profile_username_hint">Add your username</string>
|
<string name="txt_create_profile_username_hint">Add your username</string>
|
||||||
<string name="txt_create_profile_username_save">Save to Squad</string>
|
<string name="txt_create_profile_username_save">Save to Squad</string>
|
||||||
<string name="txt_create_profile_show_stats">Show Stats</string>
|
<string name="txt_create_profile_show_stats">Show Stats</string>
|
||||||
|
<string name="txt_primary_user_label">Set as Primary User</string>
|
||||||
<string name="txt_update_profile_header">Update Profile</string>
|
<string name="txt_update_profile_header">Update Profile</string>
|
||||||
<string name="txt_update_profile_username_save">Update Squad</string>
|
<string name="txt_update_profile_username_save">Update Squad</string>
|
||||||
<string name="txt_cancel_crop">Cancel</string>
|
<string name="txt_cancel_crop">Cancel</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user