diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ec791f8..74ec68d 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -46,4 +46,5 @@ dependencies {
implementation(libs.room.runtime)
annotationProcessor(libs.room.compiler)
implementation(libs.preferences)
+ implementation(libs.konfetti)
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 192a615..e1ca4a6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,6 +7,8 @@
-->
+
+
0 && turnTotal == 180) {
+ final Animation shakeAnimation = AnimationUtils.loadAnimation(this, R.anim.shake);
+ final View main = findViewById(R.id.main);
+ main.startAnimation(shakeAnimation);
+
+ final Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
+ if (vibrator != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ vibrator.vibrate(VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE));
+ }
+ mSoundEngine.playOneHundredAndEightySound();
+ }
// Re-check logic for non-double finish or score of 1
int lastDartValue = mCurrentTurnDarts.get(mCurrentTurnDarts.size() - 1);
@@ -579,17 +609,47 @@ public class GameActivity extends AppCompatActivity {
updatePlayerStats(winner, dartsThrown, pointsMade, false);
// Show win notification
Toast.makeText(this, winner.name + " WINS!", Toast.LENGTH_LONG).show();
-
- // End game and return to previous screen
- finish();
+
+ playWinnerAnimation(winner.name);
+ mSoundEngine.playWinnerSound();
// TODO: Consider adding:
- // - Win animation/sound
// - Statistics display
// - Save match to database
// - Offer rematch
}
+ private void playWinnerAnimation(final String winnerName) {
+ final KonfettiView konfettiView = findViewById(R.id.konfetti_view);
+ final View dimmerLayout = findViewById(R.id.dimmer);
+ final TextView winnerText = findViewById(R.id.winner_text);
+
+ dimmerLayout.setVisibility(View.VISIBLE);
+ final EmitterConfig emitterConfig = new Emitter(300, TimeUnit.MILLISECONDS).max(300);
+ konfettiView.start(
+ new PartyFactory(emitterConfig)
+ .shapes(Shape.Circle.INSTANCE, Shape.Square.INSTANCE)
+ .spread(360)
+ .position(0.5, 0.5) // Center of screen
+ .colors(Arrays.asList(0xfce18a, 0xff726d, 0xf4306d, 0xb48def)) // Gold/Festive colors
+ .build()
+ );
+
+ winnerText.setText(winnerName + " WINS!");
+ winnerText.setVisibility(View.VISIBLE);
+ winnerText.setScaleX(0f);
+ winnerText.setScaleY(0f);
+ winnerText.setAlpha(0f);
+
+ winnerText.animate()
+ .scaleX(1.2f)
+ .scaleY(1.2f)
+ .alpha(1.0f)
+ .setDuration(500)
+ .setInterpolator(new OvershootInterpolator())
+ .start();
+ }
+
/**
* State holder for a single player's X01 game progress.
* Tracks name, remaining score, and darts thrown.
diff --git a/app/src/main/java/com/aldo/apps/ochecompanion/utils/SoundEngine.java b/app/src/main/java/com/aldo/apps/ochecompanion/utils/SoundEngine.java
new file mode 100644
index 0000000..c9772d5
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/ochecompanion/utils/SoundEngine.java
@@ -0,0 +1,76 @@
+package com.aldo.apps.ochecompanion.utils;
+
+import android.content.Context;
+import android.media.SoundPool;
+import android.util.Log;
+
+import com.aldo.apps.ochecompanion.R;
+
+import java.lang.ref.WeakReference;
+import java.util.SimpleTimeZone;
+
+public final class SoundEngine {
+
+ /**
+ * Tag for debugging purpose.
+ */
+ private static final String TAG = "SoundEngine";
+
+ private Context mContext;
+
+ private static SoundEngine sInstance;
+
+ private final SoundPool mSoundPool;
+
+ private boolean mIsReady;
+
+ private int mWinnerSoundId;
+
+ private int mBustedSoundId;
+
+ private int m180SoundId;
+
+ private SoundEngine(final Context context) {
+ mContext = context;
+ mSoundPool = new SoundPool.Builder().setMaxStreams(5).build();
+ mSoundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> mIsReady = true);
+ mWinnerSoundId = mSoundPool.load(context, R.raw.winner, 1);
+ mBustedSoundId = mSoundPool.load(context, R.raw.busted, 1);
+ m180SoundId = mSoundPool.load(context, R.raw.onehundredandeighty, 1);
+
+ }
+
+ public static SoundEngine getInstance(final Context context) {
+ if (sInstance == null) {
+ sInstance = new SoundEngine(context);
+ }
+ return sInstance;
+ }
+
+ public void playWinnerSound() {
+ if (mIsReady) {
+ mSoundPool.play(mWinnerSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
+ } else {
+ Log.w(TAG, "playWinnerSound: Not yet ready, cannot play sounds.");
+ }
+ }
+
+ public void playBustedSound() {
+ if (mIsReady) {
+ mSoundPool.play(mBustedSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
+ } else {
+ Log.w(TAG, "playBustedSound: Not yet ready, cannot play sounds.");
+ }
+ }
+
+ public void playOneHundredAndEightySound() {
+ if (mIsReady) {
+ mSoundPool.play(m180SoundId, 1.0f, 1.0f, 0, 0, 1.0f);
+ } else {
+ Log.w(TAG, "playOneHundredAndEightySound: Not yet ready, cannot play sounds.");
+ }
+ }
+
+
+
+}
diff --git a/app/src/main/res/anim/shake.xml b/app/src/main/res/anim/shake.xml
new file mode 100644
index 0000000..f3d3989
--- /dev/null
+++ b/app/src/main/res/anim/shake.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_game.xml b/app/src/main/res/layout/activity_game.xml
index 2ee68cd..0a23158 100644
--- a/app/src/main/res/layout/activity_game.xml
+++ b/app/src/main/res/layout/activity_game.xml
@@ -157,4 +157,30 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/raw/busted.mp3 b/app/src/main/res/raw/busted.mp3
new file mode 100644
index 0000000..6c4c70a
Binary files /dev/null and b/app/src/main/res/raw/busted.mp3 differ
diff --git a/app/src/main/res/raw/onehundredandeighty.mp3 b/app/src/main/res/raw/onehundredandeighty.mp3
new file mode 100644
index 0000000..d2b319f
Binary files /dev/null and b/app/src/main/res/raw/onehundredandeighty.mp3 differ
diff --git a/app/src/main/res/raw/sad_busted.mp3 b/app/src/main/res/raw/sad_busted.mp3
new file mode 100644
index 0000000..f9c3c1f
Binary files /dev/null and b/app/src/main/res/raw/sad_busted.mp3 differ
diff --git a/app/src/main/res/raw/winner.mp3 b/app/src/main/res/raw/winner.mp3
new file mode 100644
index 0000000..9a18576
Binary files /dev/null and b/app/src/main/res/raw/winner.mp3 differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index b8947fa..6d7b333 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -6,6 +6,7 @@
#FFFFFF
#E5E5EA
#0A0A0A
+ #BF313131
#0056B3
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e9f7907..e843518 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -9,8 +9,10 @@ activity = "1.12.2"
constraintlayout = "2.2.1"
glide = "5.0.5"
room = "2.8.4"
-preferences = "1.2.0"
+preferences = "1.2.1"
preference = "1.2.1"
+konfetti = "2.0.5"
+
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -25,7 +27,7 @@ room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room"}
preferences = { group = "androidx.preference", name="preference-ktx", version.ref="preferences" }
preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
+konfetti = { group = "nl.dionsegijn", name = "konfetti-xml", version.ref = "konfetti" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
-