Added Animation and Sound for Game End
This commit is contained in:
@@ -46,4 +46,5 @@ dependencies {
|
|||||||
implementation(libs.room.runtime)
|
implementation(libs.room.runtime)
|
||||||
annotationProcessor(libs.room.compiler)
|
annotationProcessor(libs.room.compiler)
|
||||||
implementation(libs.preferences)
|
implementation(libs.preferences)
|
||||||
|
implementation(libs.konfetti)
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.media.SoundPool;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.Vibrator;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.AlphaAnimation;
|
import android.view.animation.AlphaAnimation;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.view.animation.OvershootInterpolator;
|
||||||
import android.widget.GridLayout;
|
import android.widget.GridLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -26,11 +32,20 @@ import com.aldo.apps.ochecompanion.database.objects.Statistics;
|
|||||||
import com.aldo.apps.ochecompanion.utils.CheckoutConstants;
|
import com.aldo.apps.ochecompanion.utils.CheckoutConstants;
|
||||||
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.UIConstants;
|
import com.aldo.apps.ochecompanion.utils.UIConstants;
|
||||||
import com.google.android.material.button.MaterialButton;
|
import com.google.android.material.button.MaterialButton;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import nl.dionsegijn.konfetti.core.PartyFactory;
|
||||||
|
import nl.dionsegijn.konfetti.core.emitter.Emitter;
|
||||||
|
import nl.dionsegijn.konfetti.core.emitter.EmitterConfig;
|
||||||
|
import nl.dionsegijn.konfetti.core.models.Shape;
|
||||||
|
import nl.dionsegijn.konfetti.xml.KonfettiView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main game activity for playing X01 darts games (501, 301, etc.).
|
* Main game activity for playing X01 darts games (501, 301, etc.).
|
||||||
@@ -161,6 +176,8 @@ public class GameActivity extends AppCompatActivity {
|
|||||||
*/
|
*/
|
||||||
private GridLayout glKeyboard;
|
private GridLayout glKeyboard;
|
||||||
|
|
||||||
|
private SoundEngine mSoundEngine;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts GameActivity with specified players and starting score.
|
* Starts GameActivity with specified players and starting score.
|
||||||
*
|
*
|
||||||
@@ -192,6 +209,7 @@ public class GameActivity extends AppCompatActivity {
|
|||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||||
return insets;
|
return insets;
|
||||||
});
|
});
|
||||||
|
mSoundEngine = SoundEngine.getInstance(this);
|
||||||
|
|
||||||
// Extract game parameters from intent
|
// Extract game parameters from intent
|
||||||
mStartingScore = getIntent().getIntExtra(EXTRA_START_SCORE, DartsConstants.DEFAULT_GAME_SCORE);
|
mStartingScore = getIntent().getIntExtra(EXTRA_START_SCORE, DartsConstants.DEFAULT_GAME_SCORE);
|
||||||
@@ -308,6 +326,7 @@ public class GameActivity extends AppCompatActivity {
|
|||||||
mCurrentTurnDarts.add(points);
|
mCurrentTurnDarts.add(points);
|
||||||
updateTurnIndicators();
|
updateTurnIndicators();
|
||||||
mIsTurnOver = true;
|
mIsTurnOver = true;
|
||||||
|
mSoundEngine.playBustedSound();
|
||||||
Toast.makeText(this, "BUST!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "BUST!", Toast.LENGTH_SHORT).show();
|
||||||
// In a pro interface, we usually wait for "Submit" or auto-submit after a short delay
|
// In a pro interface, we usually wait for "Submit" or auto-submit after a short delay
|
||||||
} else if (scoreAfterDart == 0 && isDouble) {
|
} else if (scoreAfterDart == 0 && isDouble) {
|
||||||
@@ -417,6 +436,17 @@ public class GameActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
// Calculate what final score would be
|
// Calculate what final score would be
|
||||||
int finalScore = active.remainingScore - turnTotal;
|
int finalScore = active.remainingScore - turnTotal;
|
||||||
|
if (finalScore > 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
|
// Re-check logic for non-double finish or score of 1
|
||||||
int lastDartValue = mCurrentTurnDarts.get(mCurrentTurnDarts.size() - 1);
|
int lastDartValue = mCurrentTurnDarts.get(mCurrentTurnDarts.size() - 1);
|
||||||
@@ -579,17 +609,47 @@ public class GameActivity extends AppCompatActivity {
|
|||||||
updatePlayerStats(winner, dartsThrown, pointsMade, false);
|
updatePlayerStats(winner, dartsThrown, pointsMade, false);
|
||||||
// Show win notification
|
// Show win notification
|
||||||
Toast.makeText(this, winner.name + " WINS!", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, winner.name + " WINS!", Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
// End game and return to previous screen
|
playWinnerAnimation(winner.name);
|
||||||
finish();
|
mSoundEngine.playWinnerSound();
|
||||||
|
|
||||||
// TODO: Consider adding:
|
// TODO: Consider adding:
|
||||||
// - Win animation/sound
|
|
||||||
// - Statistics display
|
// - Statistics display
|
||||||
// - Save match to database
|
// - Save match to database
|
||||||
// - Offer rematch
|
// - 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.
|
* State holder for a single player's X01 game progress.
|
||||||
* Tracks name, remaining score, and darts thrown.
|
* Tracks name, remaining score, and darts thrown.
|
||||||
|
|||||||
@@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
8
app/src/main/res/anim/shake.xml
Normal file
8
app/src/main/res/anim/shake.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="50"
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:toXDelta="15"
|
||||||
|
android:repeatCount="8"
|
||||||
|
android:repeatMode="reverse"
|
||||||
|
android:interpolator="@android:anim/linear_interpolator" />
|
||||||
@@ -157,4 +157,30 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/dimmer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/dim_color"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/winner_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
style="@style/TextAppearance.Oche.Hero"
|
||||||
|
android:textColor="@color/volt_green"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Zander"/>
|
||||||
|
|
||||||
|
<nl.dionsegijn.konfetti.xml.KonfettiView
|
||||||
|
android:id="@+id/konfetti_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
BIN
app/src/main/res/raw/busted.mp3
Normal file
BIN
app/src/main/res/raw/busted.mp3
Normal file
Binary file not shown.
BIN
app/src/main/res/raw/onehundredandeighty.mp3
Normal file
BIN
app/src/main/res/raw/onehundredandeighty.mp3
Normal file
Binary file not shown.
BIN
app/src/main/res/raw/sad_busted.mp3
Normal file
BIN
app/src/main/res/raw/sad_busted.mp3
Normal file
Binary file not shown.
BIN
app/src/main/res/raw/winner.mp3
Normal file
BIN
app/src/main/res/raw/winner.mp3
Normal file
Binary file not shown.
@@ -6,6 +6,7 @@
|
|||||||
<color name="surface_primary">#FFFFFF</color> <!-- Pure white cards -->
|
<color name="surface_primary">#FFFFFF</color> <!-- Pure white cards -->
|
||||||
<color name="surface_secondary">#E5E5EA</color> <!-- Light gray inputs -->
|
<color name="surface_secondary">#E5E5EA</color> <!-- Light gray inputs -->
|
||||||
<color name="midnight_black">#0A0A0A</color> <!-- Real black for OLED DIsplays -->
|
<color name="midnight_black">#0A0A0A</color> <!-- Real black for OLED DIsplays -->
|
||||||
|
<color name="dim_color">#BF313131</color>
|
||||||
|
|
||||||
<!-- Contextual Colors -->
|
<!-- Contextual Colors -->
|
||||||
<color name="triple_blue">#0056B3</color>
|
<color name="triple_blue">#0056B3</color>
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ activity = "1.12.2"
|
|||||||
constraintlayout = "2.2.1"
|
constraintlayout = "2.2.1"
|
||||||
glide = "5.0.5"
|
glide = "5.0.5"
|
||||||
room = "2.8.4"
|
room = "2.8.4"
|
||||||
preferences = "1.2.0"
|
preferences = "1.2.1"
|
||||||
preference = "1.2.1"
|
preference = "1.2.1"
|
||||||
|
konfetti = "2.0.5"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
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"}
|
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room"}
|
||||||
preferences = { group = "androidx.preference", name="preference-ktx", version.ref="preferences" }
|
preferences = { group = "androidx.preference", name="preference-ktx", version.ref="preferences" }
|
||||||
preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
|
preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
|
||||||
|
konfetti = { group = "nl.dionsegijn", name = "konfetti-xml", version.ref = "konfetti" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user