Added a Player stats view to the GameResult
Currently only showing the stats of the winning player and also only the overall stats, has to be improved to show stats of all players and also only the stats of the match, not the overall one.
This commit is contained in:
@@ -29,6 +29,7 @@ import androidx.preference.PreferenceManager;
|
|||||||
import com.aldo.apps.ochecompanion.database.AppDatabase;
|
import com.aldo.apps.ochecompanion.database.AppDatabase;
|
||||||
import com.aldo.apps.ochecompanion.database.objects.Player;
|
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||||
import com.aldo.apps.ochecompanion.database.objects.Statistics;
|
import com.aldo.apps.ochecompanion.database.objects.Statistics;
|
||||||
|
import com.aldo.apps.ochecompanion.ui.PlayerStatsView;
|
||||||
import com.aldo.apps.ochecompanion.utils.CheckoutEngine;
|
import com.aldo.apps.ochecompanion.utils.CheckoutEngine;
|
||||||
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
||||||
import com.aldo.apps.ochecompanion.utils.SoundEngine;
|
import com.aldo.apps.ochecompanion.utils.SoundEngine;
|
||||||
@@ -198,6 +199,16 @@ public class GameActivity extends AppCompatActivity {
|
|||||||
*/
|
*/
|
||||||
private View btnTriple;
|
private View btnTriple;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Button to open the stats view.
|
||||||
|
*/
|
||||||
|
private MaterialButton mShowStatsBtn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link PlayerStatsView} to display player statistics.
|
||||||
|
*/
|
||||||
|
private PlayerStatsView mStatsView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of three TextViews showing darts thrown in current turn.
|
* Array of three TextViews showing darts thrown in current turn.
|
||||||
*/
|
*/
|
||||||
@@ -316,6 +327,14 @@ public class GameActivity extends AppCompatActivity {
|
|||||||
btnDouble.setOnClickListener(v -> setMultiplier(2));
|
btnDouble.setOnClickListener(v -> setMultiplier(2));
|
||||||
btnTriple.setOnClickListener(v -> setMultiplier(3));
|
btnTriple.setOnClickListener(v -> setMultiplier(3));
|
||||||
|
|
||||||
|
mShowStatsBtn = findViewById(R.id.show_stats_btn);
|
||||||
|
mStatsView = findViewById(R.id.player_stats_view);
|
||||||
|
mShowStatsBtn.setOnClickListener(v -> {
|
||||||
|
mStatsView.setVisibility(View.VISIBLE);
|
||||||
|
mShowStatsBtn.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
findViewById(R.id.btnSubmitTurn).setOnClickListener(v -> submitTurn());
|
findViewById(R.id.btnSubmitTurn).setOnClickListener(v -> submitTurn());
|
||||||
findViewById(R.id.btnUndoDart).setOnClickListener(v -> undoLastDart());
|
findViewById(R.id.btnUndoDart).setOnClickListener(v -> undoLastDart());
|
||||||
}
|
}
|
||||||
@@ -876,12 +895,27 @@ public class GameActivity extends AppCompatActivity {
|
|||||||
mSoundEngine.playWinnerSound();
|
mSoundEngine.playWinnerSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mShowStatsBtn.setVisibility(View.VISIBLE);
|
||||||
|
attachPlayerStats();
|
||||||
// TODO: Consider adding:
|
// TODO: Consider adding:
|
||||||
// - Statistics display
|
|
||||||
// - Save match to database
|
// - Save match to database
|
||||||
// - Offer rematch
|
// - Offer rematch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void attachPlayerStats() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
final Player player = mPlayerStates.get(mActivePlayerIndex).player;
|
||||||
|
final Statistics statistics = AppDatabase.getDatabase(GameActivity.this).statisticsDao().getStatisticsForPlayer(player.id);
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
mStatsView.bind(player, statistics);
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "attachPlayerStats: Failed to increment matches", e);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays confetti animation and displays winner's name overlay.
|
* Plays confetti animation and displays winner's name overlay.
|
||||||
* Shows full-screen dimmer with celebratory confetti effect.
|
* Shows full-screen dimmer with celebratory confetti effect.
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.aldo.apps.ochecompanion.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.aldo.apps.ochecompanion.R;
|
||||||
|
import com.aldo.apps.ochecompanion.database.objects.Player;
|
||||||
|
import com.aldo.apps.ochecompanion.database.objects.Statistics;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.google.android.material.imageview.ShapeableImageView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PlayerStatsView: A complete dashboard component that visualizes a player's
|
||||||
|
* career performance, including a heatmap and detailed metrics.
|
||||||
|
*/
|
||||||
|
public class PlayerStatsView extends ScrollView {
|
||||||
|
|
||||||
|
// UI References
|
||||||
|
private HeatmapView mHeatmap;
|
||||||
|
private ShapeableImageView mIvAvatar;
|
||||||
|
private TextView mTvUsername, mTvCareerAvg, mTvFirst9, mTvCheckoutPct, mTvBestFinish;
|
||||||
|
private TextView mTvCount60, mTvCount100, mTvCount140, mTvCount180;
|
||||||
|
|
||||||
|
public PlayerStatsView(@NonNull final Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerStatsView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.player_stats_layout, this, true);
|
||||||
|
initViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews() {
|
||||||
|
mHeatmap = findViewById(R.id.statsHeatmap);
|
||||||
|
mIvAvatar = findViewById(R.id.ivPlayerAvatar);
|
||||||
|
mTvUsername = findViewById(R.id.tvUsername);
|
||||||
|
mTvCareerAvg = findViewById(R.id.tvCareerAvgValue);
|
||||||
|
mTvFirst9 = findViewById(R.id.tvFirst9Value);
|
||||||
|
mTvCheckoutPct = findViewById(R.id.tvCheckoutPctValue);
|
||||||
|
mTvBestFinish = findViewById(R.id.tvBestFinishValue);
|
||||||
|
|
||||||
|
// Threshold counters
|
||||||
|
mTvCount60 = findViewById(R.id.tvCount60);
|
||||||
|
mTvCount100 = findViewById(R.id.tvCount100);
|
||||||
|
mTvCount140 = findViewById(R.id.tvCount140);
|
||||||
|
mTvCount180 = findViewById(R.id.tvCount180);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds both the player identity and their accumulated stats to the UI.
|
||||||
|
*/
|
||||||
|
public void bind(@NonNull final Player player, final @NonNull Statistics stats) {
|
||||||
|
// 1. Identity
|
||||||
|
mTvUsername.setText(player.username.toUpperCase());
|
||||||
|
if (player.profilePictureUri != null) {
|
||||||
|
Glide.with(getContext()).load(player.profilePictureUri).into(mIvAvatar);
|
||||||
|
} else {
|
||||||
|
mIvAvatar.setImageResource(R.drawable.ic_users);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. High-Level Metrics
|
||||||
|
mTvCareerAvg.setText(String.format("%.1f", stats.getAverage()));
|
||||||
|
mTvFirst9.setText(String.format("%.1f", stats.getFirst9Average()));
|
||||||
|
mTvCheckoutPct.setText(String.format("%.1f%%", stats.getCheckoutPercentage()));
|
||||||
|
mTvBestFinish.setText(String.valueOf(stats.getHighestCheckout()));
|
||||||
|
|
||||||
|
// 3. Threshold Totals
|
||||||
|
mTvCount60.setText(String.valueOf(stats.getCount60Plus()));
|
||||||
|
mTvCount100.setText(String.valueOf(stats.getCount100Plus()));
|
||||||
|
mTvCount140.setText(String.valueOf(stats.getCount140Plus()));
|
||||||
|
mTvCount180.setText(String.valueOf(stats.getCount180()));
|
||||||
|
|
||||||
|
// 4. Heatmap Rendering
|
||||||
|
mHeatmap.setStats(stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -177,10 +177,28 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:text="Zander"/>
|
tools:text="Zander"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/show_stats_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/txt_game_show_statistics"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/winner_text"
|
||||||
|
style="@style/Widget.Oche_Button_Primary"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<nl.dionsegijn.konfetti.xml.KonfettiView
|
<nl.dionsegijn.konfetti.xml.KonfettiView
|
||||||
android:id="@+id/konfetti_view"
|
android:id="@+id/konfetti_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<com.aldo.apps.ochecompanion.ui.PlayerStatsView
|
||||||
|
android:id="@+id/player_stats_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
142
app/src/main/res/layout/player_stats_layout.xml
Normal file
142
app/src/main/res/layout/player_stats_layout.xml
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<?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:orientation="vertical"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:background="@color/background_primary">
|
||||||
|
|
||||||
|
<!-- HEADER: IDENTITY -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/ivPlayerAvatar"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:padding="2dp"
|
||||||
|
app:strokeWidth="2dp"
|
||||||
|
app:strokeColor="@color/volt_green"
|
||||||
|
app:shapeAppearanceOverlay="@style/ShapeAppearance.MaterialComponents.SmallComponent"
|
||||||
|
android:src="@drawable/ic_users" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvUsername"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-black"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="22sp"
|
||||||
|
tools:text="SNAKEBITE" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="PRO ANALYTICS ACTIVE"
|
||||||
|
android:textAppearance="@style/TextAppearance.Oche.Caption"
|
||||||
|
android:textColor="@color/volt_green" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- CENTERPIECE: HEATMAP -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Impact Heatmap"
|
||||||
|
android:textAppearance="@style/TextAppearance.Oche.Caption"
|
||||||
|
android:layout_marginBottom="8dp" />
|
||||||
|
|
||||||
|
<com.aldo.apps.ochecompanion.ui.HeatmapView
|
||||||
|
android:id="@+id/statsHeatmap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="320dp"
|
||||||
|
android:layout_marginBottom="24dp" />
|
||||||
|
|
||||||
|
<!-- METRICS GRID (Top Row) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:weightSum="2">
|
||||||
|
|
||||||
|
<LinearLayout style="@style/StatsCardStyle">
|
||||||
|
<TextView android:id="@+id/tvCareerAvgValue" style="@style/StatsValueStyle" tools:text="94.2" />
|
||||||
|
<TextView android:text="Career Avg" style="@style/StatsLabelStyle" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout style="@style/StatsCardStyle" android:layout_marginStart="12dp">
|
||||||
|
<TextView android:id="@+id/tvFirst9Value" style="@style/StatsValueStyle" tools:text="102.5" />
|
||||||
|
<TextView android:text="First 9 Avg" style="@style/StatsLabelStyle" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- METRICS GRID (Bottom Row) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:weightSum="2">
|
||||||
|
|
||||||
|
<LinearLayout style="@style/StatsCardStyle">
|
||||||
|
<TextView android:id="@+id/tvCheckoutPctValue" style="@style/StatsValueStyle" tools:text="38.5%" />
|
||||||
|
<TextView android:text="Checkout %" style="@style/StatsLabelStyle" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout style="@style/StatsCardStyle" android:layout_marginStart="12dp">
|
||||||
|
<TextView android:id="@+id/tvBestFinishValue" style="@style/StatsValueStyle" tools:text="170" />
|
||||||
|
<TextView android:text="High Finish" style="@style/StatsLabelStyle" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- SCORING THRESHOLDS -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="Threshold Hits"
|
||||||
|
android:textAppearance="@style/TextAppearance.Oche.Caption"
|
||||||
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@drawable/shape_round_surface"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<LinearLayout style="@style/ThresholdItemStyle">
|
||||||
|
<TextView android:id="@+id/tvCount60" style="@style/ThresholdValueStyle" tools:text="450" />
|
||||||
|
<TextView android:text="60+" style="@style/ThresholdLabelStyle" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout style="@style/ThresholdItemStyle">
|
||||||
|
<TextView android:id="@+id/tvCount100" style="@style/ThresholdValueStyle" tools:text="112" />
|
||||||
|
<TextView android:text="100+" style="@style/ThresholdLabelStyle" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout style="@style/ThresholdItemStyle">
|
||||||
|
<TextView android:id="@+id/tvCount140" style="@style/ThresholdValueStyle" tools:text="42" />
|
||||||
|
<TextView android:text="140+" style="@style/ThresholdLabelStyle" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout style="@style/ThresholdItemStyle">
|
||||||
|
<TextView android:id="@+id/tvCount180" style="@style/ThresholdValueStyle" android:textColor="@color/volt_green" tools:text="12" />
|
||||||
|
<TextView android:text="180s" style="@style/ThresholdLabelStyle" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
<string name="txt_game_btn_triple">Triple</string>
|
<string name="txt_game_btn_triple">Triple</string>
|
||||||
<string name="txt_game_btn_bull">Bull</string>
|
<string name="txt_game_btn_bull">Bull</string>
|
||||||
<string name="txt_game_btn_submit">Submit Turn</string>
|
<string name="txt_game_btn_submit">Submit Turn</string>
|
||||||
|
<string name="txt_game_show_statistics">Show Stats</string>
|
||||||
|
|
||||||
<!-- Preference Strings -->
|
<!-- Preference Strings -->
|
||||||
<string name="pref_key_day_night_mode_auto">day_night_mode_auto</string>
|
<string name="pref_key_day_night_mode_auto">day_night_mode_auto</string>
|
||||||
|
|||||||
@@ -87,4 +87,46 @@
|
|||||||
<item name="cornerSize">12dp</item>
|
<item name="cornerSize">12dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- Stats Dashboard Specific Styles -->
|
||||||
|
<style name="StatsCardStyle">
|
||||||
|
<item name="android:layout_width">0dp</item>
|
||||||
|
<item name="android:layout_height">80dp</item>
|
||||||
|
<item name="android:layout_weight">1</item>
|
||||||
|
<item name="android:background">@drawable/shape_round_surface</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
|
<item name="android:orientation">vertical</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="StatsValueStyle">
|
||||||
|
<item name="android:layout_width">wrap_content</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:textSize">24sp</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:fontFamily">sans-serif-black</item>
|
||||||
|
<item name="android:textColor">@color/text_primary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="StatsLabelStyle">
|
||||||
|
<item name="android:layout_width">wrap_content</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:textSize">10sp</item>
|
||||||
|
<item name="android:textAllCaps">true</item>
|
||||||
|
<item name="android:textColor">@color/text_secondary</item>
|
||||||
|
<item name="android:fontFamily">sans-serif-medium</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ThresholdItemStyle">
|
||||||
|
<item name="android:layout_width">0dp</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:layout_weight">1</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
|
<item name="android:orientation">vertical</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ThresholdValueStyle" parent="StatsValueStyle">
|
||||||
|
<item name="android:textSize">18sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ThresholdLabelStyle" parent="StatsLabelStyle" />
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user