mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 09:15:20 +01:00
Compare commits
3 commits
21b2513ad4
...
14205b2b0a
Author | SHA1 | Date | |
---|---|---|---|
|
14205b2b0a | ||
|
808dc79396 | ||
|
220caa39f7 |
11 changed files with 441 additions and 15 deletions
|
@ -11,6 +11,7 @@ A modern falling block puzzle game for Android, featuring smooth animations, res
|
||||||
- Hard drop and soft drop controls
|
- Hard drop and soft drop controls
|
||||||
- Advanced move detection (e.g., T-Spins) and scoring
|
- Advanced move detection (e.g., T-Spins) and scoring
|
||||||
- Persistent high score system (Top 5)
|
- Persistent high score system (Top 5)
|
||||||
|
- Global leaderboard via Google Play Games Services
|
||||||
|
|
||||||
### Modern Android Features
|
### Modern Android Features
|
||||||
- Optimized for Android 11+ (API 30+)
|
- Optimized for Android 11+ (API 30+)
|
||||||
|
@ -20,6 +21,7 @@ A modern falling block puzzle game for Android, featuring smooth animations, res
|
||||||
- Automatic Dark theme support
|
- Automatic Dark theme support
|
||||||
- Intuitive and responsive touch controls
|
- Intuitive and responsive touch controls
|
||||||
- Full edge-to-edge display utilization
|
- Full edge-to-edge display utilization
|
||||||
|
- Google Play Games Services integration for leaderboards
|
||||||
|
|
||||||
### Scoring System
|
### Scoring System
|
||||||
|
|
||||||
|
@ -74,6 +76,12 @@ Pixelmint Drop features a comprehensive scoring system designed to reward skillf
|
||||||
- Multiple theme options with ability to change manually or enable Random Mode (unlocked when 2+ themes are available).
|
- Multiple theme options with ability to change manually or enable Random Mode (unlocked when 2+ themes are available).
|
||||||
- In Random Mode, themes change automatically every 10 line clears (1 level).
|
- In Random Mode, themes change automatically every 10 line clears (1 level).
|
||||||
|
|
||||||
|
### Online Features
|
||||||
|
- Global leaderboard support through Google Play Games Services
|
||||||
|
- Sign in with your Google account to compete with players worldwide
|
||||||
|
- Your highest scores are automatically submitted to the leaderboard
|
||||||
|
- View the global leaderboard directly from the game
|
||||||
|
|
||||||
## Technical Details
|
## Technical Details
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
|
@ -11,8 +11,8 @@ android {
|
||||||
applicationId "com.pixelmintdrop"
|
applicationId "com.pixelmintdrop"
|
||||||
minSdk 30
|
minSdk 30
|
||||||
targetSdk 35
|
targetSdk 35
|
||||||
versionCode 2
|
versionCode 5
|
||||||
versionName "0.1"
|
versionName "0.4"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,8 @@ dependencies {
|
||||||
implementation 'androidx.window:window:1.2.0' // For better display support
|
implementation 'androidx.window:window:1.2.0' // For better display support
|
||||||
implementation 'androidx.window:window-java:1.2.0'
|
implementation 'androidx.window:window-java:1.2.0'
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
|
implementation 'com.google.android.gms:play-services-games-v2:19.0.0'
|
||||||
|
implementation 'com.google.android.gms:play-services-auth:20.7.0'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<!-- Add permission to handle system gestures if needed on some devices -->
|
<!-- Add permission to handle system gestures if needed on some devices -->
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
@ -11,6 +13,12 @@
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.pixelmintdrop">
|
android:theme="@style/Theme.pixelmintdrop">
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.games.APP_ID"
|
||||||
|
android:value="@string/app_id" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.version"
|
||||||
|
android:value="@integer/google_play_services_version" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|
|
@ -7,15 +7,18 @@ import com.pixelmintdrop.databinding.HighScoresBinding
|
||||||
import com.pixelmintdrop.model.HighScoreAdapter
|
import com.pixelmintdrop.model.HighScoreAdapter
|
||||||
import com.pixelmintdrop.model.HighScoreManager
|
import com.pixelmintdrop.model.HighScoreManager
|
||||||
import com.pixelmintdrop.model.PlayerProgressionManager
|
import com.pixelmintdrop.model.PlayerProgressionManager
|
||||||
|
import com.pixelmintdrop.model.GooglePlayGamesManager
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.InputDevice
|
import android.view.InputDevice
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
class HighScoresActivity : AppCompatActivity() {
|
class HighScoresActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: HighScoresBinding
|
private lateinit var binding: HighScoresBinding
|
||||||
|
@ -24,6 +27,10 @@ class HighScoresActivity : AppCompatActivity() {
|
||||||
private lateinit var progressionManager: PlayerProgressionManager
|
private lateinit var progressionManager: PlayerProgressionManager
|
||||||
private var currentTheme = PlayerProgressionManager.THEME_CLASSIC
|
private var currentTheme = PlayerProgressionManager.THEME_CLASSIC
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val RC_LEADERBOARD_UI_HS = 9005 // Unique request code for HighScoresActivity
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -68,6 +75,9 @@ class HighScoresActivity : AppCompatActivity() {
|
||||||
binding.backButton.setOnClickListener {
|
binding.backButton.setOnClickListener {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide the leaderboard button
|
||||||
|
binding.leaderboardButton?.visibility = View.GONE
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("HighScoresActivity", "Error in onCreate", e)
|
Log.e("HighScoresActivity", "Error in onCreate", e)
|
||||||
// Show an error message if necessary, or finish gracefully
|
// Show an error message if necessary, or finish gracefully
|
||||||
|
@ -104,7 +114,7 @@ class HighScoresActivity : AppCompatActivity() {
|
||||||
else -> Color.WHITE
|
else -> Color.WHITE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply theme to back button
|
// Apply theme to buttons
|
||||||
binding.backButton.setTextColor(textColor)
|
binding.backButton.setTextColor(textColor)
|
||||||
|
|
||||||
// Update adapter theme
|
// Update adapter theme
|
||||||
|
@ -123,6 +133,35 @@ class HighScoresActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showGlobalLeaderboard() {
|
||||||
|
try {
|
||||||
|
if (!highScoreManager.isGooglePlaySignedIn()) {
|
||||||
|
Toast.makeText(this, "Signing in to Google Play Games...", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the updated showLeaderboard with a listener
|
||||||
|
highScoreManager.getGooglePlayGamesManager().showLeaderboard(this,
|
||||||
|
object : GooglePlayGamesManager.LeaderboardIntentListener {
|
||||||
|
override fun onLeaderboardIntentSuccess(intent: Intent) {
|
||||||
|
try {
|
||||||
|
startActivityForResult(intent, RC_LEADERBOARD_UI_HS)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("HighScoresActivity", "Failed to start leaderboard activity for result", e)
|
||||||
|
Toast.makeText(this@HighScoresActivity, "Could not display leaderboard.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLeaderboardIntentFailure(exception: Exception) {
|
||||||
|
Log.e("HighScoresActivity", "Failed to get leaderboard intent.", exception)
|
||||||
|
Toast.makeText(this@HighScoresActivity, "Could not display leaderboard.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("HighScoresActivity", "Error showing leaderboard", e)
|
||||||
|
Toast.makeText(this, "Could not open leaderboard", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
try {
|
try {
|
||||||
|
@ -143,6 +182,11 @@ class HighScoresActivity : AppCompatActivity() {
|
||||||
finish()
|
finish()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y -> {
|
||||||
|
// Y button shows global leaderboard
|
||||||
|
showGlobalLeaderboard()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,14 @@ import androidx.core.view.updatePadding
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
// Google Play Games Services imports
|
||||||
|
import com.google.android.gms.games.PlayGames
|
||||||
|
import com.google.android.gms.games.PlayGamesSdk
|
||||||
|
import com.google.android.gms.games.GamesSignInClient
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignIn // Added for silent sign-in
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInClient // Added for silent sign-in
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInOptions // Added for silent sign-in
|
||||||
|
import com.pixelmintdrop.model.GooglePlayGamesManager // Added import for listener
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(),
|
class MainActivity : AppCompatActivity(),
|
||||||
GamepadController.GamepadConnectionListener,
|
GamepadController.GamepadConnectionListener,
|
||||||
|
@ -63,11 +71,16 @@ class MainActivity : AppCompatActivity(),
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "MainActivity"
|
private const val TAG = "MainActivity"
|
||||||
|
private const val RC_LEADERBOARD_UI = 9004 // Request code for leaderboard UI
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewModel
|
// ViewModel
|
||||||
private val viewModel: MainActivityViewModel by viewModels() // Added ViewModel
|
private val viewModel: MainActivityViewModel by viewModels() // Added ViewModel
|
||||||
|
|
||||||
|
// Google Play Games Services
|
||||||
|
private lateinit var gamesSignInClient: GamesSignInClient
|
||||||
|
private lateinit var googleSignInClient: GoogleSignInClient // Added for silent sign-in
|
||||||
|
|
||||||
// UI components
|
// UI components
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var gameView: GameView
|
private lateinit var gameView: GameView
|
||||||
|
@ -157,6 +170,16 @@ class MainActivity : AppCompatActivity(),
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
// Initialize Google Play Games Services
|
||||||
|
PlayGamesSdk.initialize(this)
|
||||||
|
gamesSignInClient = PlayGames.getGamesSignInClient(this)
|
||||||
|
// Initialize GoogleSignInClient for silent sign-in
|
||||||
|
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build()
|
||||||
|
googleSignInClient = GoogleSignIn.getClient(this, signInOptions)
|
||||||
|
|
||||||
|
// Request sign-in silently
|
||||||
|
signInToPlayGames()
|
||||||
|
|
||||||
// Store initial padding values before applying insets
|
// Store initial padding values before applying insets
|
||||||
val initialPausePadding = Rect(binding.pauseContainer.paddingLeft, binding.pauseContainer.paddingTop,
|
val initialPausePadding = Rect(binding.pauseContainer.paddingLeft, binding.pauseContainer.paddingTop,
|
||||||
binding.pauseContainer.paddingRight, binding.pauseContainer.paddingBottom)
|
binding.pauseContainer.paddingRight, binding.pauseContainer.paddingBottom)
|
||||||
|
@ -450,6 +473,10 @@ class MainActivity : AppCompatActivity(),
|
||||||
Log.d(TAG, "[GameOverDebug] Triples: $lastSessionTriples (from manager: ${statsManager.getSessionTriples()})")
|
Log.d(TAG, "[GameOverDebug] Triples: $lastSessionTriples (from manager: ${statsManager.getSessionTriples()})")
|
||||||
Log.d(TAG, "[GameOverDebug] Quads: $lastSessionQuads (from manager: ${statsManager.getSessionQuads()})")
|
Log.d(TAG, "[GameOverDebug] Quads: $lastSessionQuads (from manager: ${statsManager.getSessionQuads()})")
|
||||||
|
|
||||||
|
// *** Submit score to Google Play Games ***
|
||||||
|
Log.d(TAG, "Attempting to submit end-game score to Play Games: $lastSessionScore")
|
||||||
|
highScoreManager.getGooglePlayGamesManager().submitScore(lastSessionScore.toLong(), this@MainActivity)
|
||||||
|
|
||||||
// End the session (updates lifetime stats)
|
// End the session (updates lifetime stats)
|
||||||
statsManager.endSession()
|
statsManager.endSession()
|
||||||
|
|
||||||
|
@ -529,9 +556,16 @@ class MainActivity : AppCompatActivity(),
|
||||||
startGame()
|
startGame()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// High Scores button
|
||||||
binding.highScoresButton.setOnClickListener {
|
binding.highScoresButton.setOnClickListener {
|
||||||
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
binding.pauseContainer.visibility = View.GONE
|
||||||
showHighScores()
|
val intent = Intent(this, HighScoresActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaderboard button
|
||||||
|
binding.leaderboardButton?.setOnClickListener {
|
||||||
|
showLeaderboard()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.pauseLevelUpButton.setOnClickListener {
|
binding.pauseLevelUpButton.setOnClickListener {
|
||||||
|
@ -815,6 +849,7 @@ class MainActivity : AppCompatActivity(),
|
||||||
binding.pauseRestartButton.setTextColor(themeColor)
|
binding.pauseRestartButton.setTextColor(themeColor)
|
||||||
binding.resumeButton.setTextColor(themeColor)
|
binding.resumeButton.setTextColor(themeColor)
|
||||||
binding.highScoresButton.setTextColor(themeColor)
|
binding.highScoresButton.setTextColor(themeColor)
|
||||||
|
binding.leaderboardButton?.setTextColor(themeColor)
|
||||||
binding.statsButton.setTextColor(themeColor)
|
binding.statsButton.setTextColor(themeColor)
|
||||||
binding.pauseLevelUpButton?.setTextColor(themeColor) // Safe call
|
binding.pauseLevelUpButton?.setTextColor(themeColor) // Safe call
|
||||||
binding.pauseLevelDownButton?.setTextColor(themeColor) // Safe call
|
binding.pauseLevelDownButton?.setTextColor(themeColor) // Safe call
|
||||||
|
@ -1046,6 +1081,10 @@ class MainActivity : AppCompatActivity(),
|
||||||
Log.d(TAG, "[GameOverDebug] Triples: $lastSessionTriples (from manager: ${statsManager.getSessionTriples()})")
|
Log.d(TAG, "[GameOverDebug] Triples: $lastSessionTriples (from manager: ${statsManager.getSessionTriples()})")
|
||||||
Log.d(TAG, "[GameOverDebug] Quads: $lastSessionQuads (from manager: ${statsManager.getSessionQuads()})")
|
Log.d(TAG, "[GameOverDebug] Quads: $lastSessionQuads (from manager: ${statsManager.getSessionQuads()})")
|
||||||
|
|
||||||
|
// *** Submit score to Google Play Games ***
|
||||||
|
Log.d(TAG, "Attempting to submit end-game score to Play Games: $lastSessionScore")
|
||||||
|
highScoreManager.getGooglePlayGamesManager().submitScore(lastSessionScore.toLong(), this@MainActivity)
|
||||||
|
|
||||||
// End the session (updates lifetime stats)
|
// End the session (updates lifetime stats)
|
||||||
statsManager.endSession()
|
statsManager.endSession()
|
||||||
|
|
||||||
|
@ -1640,6 +1679,7 @@ class MainActivity : AppCompatActivity(),
|
||||||
|
|
||||||
// Group 2: Stats and Scoring
|
// Group 2: Stats and Scoring
|
||||||
orderedViews.add(binding.highScoresButton)
|
orderedViews.add(binding.highScoresButton)
|
||||||
|
orderedViews.add(binding.leaderboardButton)
|
||||||
orderedViews.add(binding.statsButton)
|
orderedViews.add(binding.statsButton)
|
||||||
|
|
||||||
// Group 3: Level selection (use safe calls)
|
// Group 3: Level selection (use safe calls)
|
||||||
|
@ -2206,4 +2246,82 @@ class MainActivity : AppCompatActivity(),
|
||||||
Log.d("RandomMode", "Cannot apply random theme/skin - no unlocked options available")
|
Log.d("RandomMode", "Cannot apply random theme/skin - no unlocked options available")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles sign-in to Google Play Games Services
|
||||||
|
*/
|
||||||
|
private fun signInToPlayGames() {
|
||||||
|
Log.d(TAG, "Attempting to sign into Google Play Games")
|
||||||
|
gamesSignInClient.isAuthenticated.addOnCompleteListener { task ->
|
||||||
|
val isAuthenticated = task.isSuccessful && task.result.isAuthenticated
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
// Silent sign-in failed, but don't prompt right away
|
||||||
|
Log.d(TAG, "Not authenticated with Play Games, will prompt for sign-in when needed")
|
||||||
|
|
||||||
|
// Optional: Add a sign-in button to your menu or UI for manual sign-in
|
||||||
|
// We'll attempt sign-in when accessing leaderboards or submitting scores
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Already authenticated with Play Games")
|
||||||
|
|
||||||
|
// If we were already authenticated, make sure scores are synced
|
||||||
|
val lastHighScore = highScoreManager.getHighScores().maxByOrNull { it.score }
|
||||||
|
lastHighScore?.let {
|
||||||
|
// Submit the high score if we're already authenticated
|
||||||
|
Log.d(TAG, "User is signed in, submitting last high score: ${it.score}")
|
||||||
|
highScoreManager.getGooglePlayGamesManager().submitScore(it.score.toLong(), this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to show Google Play Games leaderboard
|
||||||
|
private fun showLeaderboard() {
|
||||||
|
// New logic: Try silent sign-in first, then interactive if needed.
|
||||||
|
Log.d(TAG, "Attempting silent sign-in before showing leaderboard...")
|
||||||
|
// Use googleSignInClient for silentSignIn
|
||||||
|
googleSignInClient.silentSignIn().addOnCompleteListener { silentSignInTask ->
|
||||||
|
if (silentSignInTask.isSuccessful) {
|
||||||
|
// Silent sign-in successful OR user already signed in.
|
||||||
|
Log.d(TAG, "Silent sign-in successful/already signed in. Getting leaderboard intent.")
|
||||||
|
getAndShowLeaderboardIntent()
|
||||||
|
} else {
|
||||||
|
// Silent sign-in failed. Need explicit sign-in.
|
||||||
|
Log.w(TAG, "Silent sign-in failed. Attempting interactive sign-in.", silentSignInTask.exception)
|
||||||
|
// Use gamesSignInClient for interactive signIn
|
||||||
|
gamesSignInClient.signIn().addOnCompleteListener { interactiveSignInTask ->
|
||||||
|
if (interactiveSignInTask.isSuccessful) {
|
||||||
|
// Interactive sign-in successful
|
||||||
|
Log.d(TAG, "Interactive sign-in successful. Getting leaderboard intent.")
|
||||||
|
getAndShowLeaderboardIntent()
|
||||||
|
} else {
|
||||||
|
// Interactive sign-in failed
|
||||||
|
Log.e(TAG, "Interactive sign-in failed.", interactiveSignInTask.exception)
|
||||||
|
Toast.makeText(this, "Sign-in failed. Please try again.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get the intent from GooglePlayGamesManager and show it
|
||||||
|
private fun getAndShowLeaderboardIntent() {
|
||||||
|
highScoreManager.getGooglePlayGamesManager().showLeaderboard(this,
|
||||||
|
object : GooglePlayGamesManager.LeaderboardIntentListener {
|
||||||
|
override fun onLeaderboardIntentSuccess(intent: Intent) {
|
||||||
|
Log.d(TAG, "Received leaderboard intent, starting activity for result.")
|
||||||
|
try {
|
||||||
|
startActivityForResult(intent, RC_LEADERBOARD_UI)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to start leaderboard activity for result", e)
|
||||||
|
Toast.makeText(this@MainActivity, "Could not display leaderboard.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLeaderboardIntentFailure(exception: Exception) {
|
||||||
|
Log.e(TAG, "Failed to get leaderboard intent.", exception)
|
||||||
|
Toast.makeText(this@MainActivity, "Could not display leaderboard.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
package com.pixelmintdrop.model
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInClient
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||||
|
import com.google.android.gms.games.PlayGames
|
||||||
|
import com.google.android.gms.games.PlayGamesSdk
|
||||||
|
import com.google.android.gms.tasks.Task
|
||||||
|
|
||||||
|
class GooglePlayGamesManager(private val context: Context) {
|
||||||
|
private val TAG = "GooglePlayGamesManager"
|
||||||
|
|
||||||
|
// Define a listener interface for the leaderboard intent
|
||||||
|
interface LeaderboardIntentListener {
|
||||||
|
fun onLeaderboardIntentSuccess(intent: Intent)
|
||||||
|
fun onLeaderboardIntentFailure(exception: Exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaderboard ID
|
||||||
|
companion object {
|
||||||
|
const val LEADERBOARD_ID = "CgkImJW2mKsSEAIQAQ"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the Play Games SDK
|
||||||
|
init {
|
||||||
|
try {
|
||||||
|
PlayGamesSdk.initialize(context)
|
||||||
|
Log.d(TAG, "PlayGamesSdk initialized")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error initializing PlayGamesSdk", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is already signed in
|
||||||
|
fun isSignedIn(): Boolean {
|
||||||
|
val account = GoogleSignIn.getLastSignedInAccount(context)
|
||||||
|
return account != null && !account.isExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the sign-in client
|
||||||
|
fun getSignInClient(): GoogleSignInClient {
|
||||||
|
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build()
|
||||||
|
return GoogleSignIn.getClient(context, signInOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle sign-in result
|
||||||
|
fun handleSignInResult(task: Task<GoogleSignInAccount>): Boolean {
|
||||||
|
return if (task.isSuccessful) {
|
||||||
|
Log.d(TAG, "Sign-in successful")
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Sign-in failed", task.exception)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit score to leaderboard (requires an activity for potential sign-in)
|
||||||
|
fun submitScore(score: Long, activity: Activity? = null) {
|
||||||
|
if (activity == null) {
|
||||||
|
Log.w(TAG, "Activity context is required for score submission.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Attempting to submit score: $score")
|
||||||
|
|
||||||
|
// Always force a sign-in to ensure we have a valid account
|
||||||
|
val signInClient = PlayGames.getGamesSignInClient(activity)
|
||||||
|
signInClient.signIn().addOnCompleteListener { signInTask ->
|
||||||
|
if (signInTask.isSuccessful) {
|
||||||
|
Log.d(TAG, "Sign-in successful, retrieving account for score submission")
|
||||||
|
|
||||||
|
// After sign-in, get the account and submit score
|
||||||
|
try {
|
||||||
|
// Use PlayGames API directly to get client and submit score
|
||||||
|
val leaderboardsClient = PlayGames.getLeaderboardsClient(activity)
|
||||||
|
leaderboardsClient.submitScore(LEADERBOARD_ID, score)
|
||||||
|
Log.d(TAG, "Successfully submitted score $score to leaderboard $LEADERBOARD_ID")
|
||||||
|
|
||||||
|
// Show confirmation to user
|
||||||
|
android.widget.Toast.makeText(
|
||||||
|
activity,
|
||||||
|
"Score submitted to leaderboard",
|
||||||
|
android.widget.Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error submitting score after sign-in", e)
|
||||||
|
android.widget.Toast.makeText(
|
||||||
|
activity,
|
||||||
|
"Failed to submit score to leaderboard",
|
||||||
|
android.widget.Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Sign-in failed, unable to submit score", signInTask.exception)
|
||||||
|
android.widget.Toast.makeText(
|
||||||
|
activity,
|
||||||
|
"Sign-in required to submit score to leaderboard",
|
||||||
|
android.widget.Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the leaderboard - Now uses a listener to return the Intent
|
||||||
|
fun showLeaderboard(activity: Activity, listener: LeaderboardIntentListener) {
|
||||||
|
Log.d(TAG, "showLeaderboard called in GooglePlayGamesManager")
|
||||||
|
|
||||||
|
// First check if already authenticated
|
||||||
|
PlayGames.getGamesSignInClient(activity).isAuthenticated.addOnCompleteListener { authTask ->
|
||||||
|
val isAuthenticated = authTask.isSuccessful && authTask.result.isAuthenticated
|
||||||
|
|
||||||
|
if (isAuthenticated) {
|
||||||
|
// Already authenticated, get leaderboard
|
||||||
|
getLeaderboardIntent(activity, listener)
|
||||||
|
} else {
|
||||||
|
// Need to authenticate first
|
||||||
|
Log.d(TAG, "User not authenticated for leaderboard, attempting sign-in")
|
||||||
|
PlayGames.getGamesSignInClient(activity).signIn().addOnCompleteListener { signInTask ->
|
||||||
|
if (signInTask.isSuccessful) {
|
||||||
|
// Successfully signed in, now get leaderboard
|
||||||
|
Log.d(TAG, "Sign-in successful, getting leaderboard")
|
||||||
|
getLeaderboardIntent(activity, listener)
|
||||||
|
} else {
|
||||||
|
// Failed to sign in
|
||||||
|
Log.e(TAG, "Failed to sign in for leaderboard access", signInTask.exception)
|
||||||
|
listener.onLeaderboardIntentFailure(
|
||||||
|
Exception("Sign-in required to view leaderboard", signInTask.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Inform user
|
||||||
|
android.widget.Toast.makeText(
|
||||||
|
activity,
|
||||||
|
"Sign-in required to view leaderboard",
|
||||||
|
android.widget.Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private helper to get leaderboard intent
|
||||||
|
private fun getLeaderboardIntent(activity: Activity, listener: LeaderboardIntentListener) {
|
||||||
|
Log.d(TAG, "Attempting to get LeaderboardsClient")
|
||||||
|
try {
|
||||||
|
val leaderboardsClient = PlayGames.getLeaderboardsClient(activity)
|
||||||
|
Log.d(TAG, "LeaderboardsClient obtained, attempting to get leaderboard intent for ID: $LEADERBOARD_ID")
|
||||||
|
leaderboardsClient
|
||||||
|
.getLeaderboardIntent(LEADERBOARD_ID)
|
||||||
|
.addOnSuccessListener { intent ->
|
||||||
|
Log.d(TAG, "Successfully obtained leaderboard intent. Calling listener.")
|
||||||
|
listener.onLeaderboardIntentSuccess(intent)
|
||||||
|
}
|
||||||
|
.addOnFailureListener { e ->
|
||||||
|
Log.e(TAG, "Error getting leaderboard intent", e)
|
||||||
|
listener.onLeaderboardIntentFailure(e)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error obtaining LeaderboardsClient or getting leaderboard intent", e)
|
||||||
|
listener.onLeaderboardIntentFailure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ class HighScoreManager(private val context: Context) {
|
||||||
private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
private val type: Type = object : TypeToken<List<HighScore>>() {}.type
|
private val type: Type = object : TypeToken<List<HighScore>>() {}.type
|
||||||
|
private val googlePlayGamesManager = GooglePlayGamesManager(context)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PREFS_NAME = "pixelmintdrop_highscores"
|
private const val PREFS_NAME = "pixelmintdrop_highscores"
|
||||||
|
@ -27,6 +28,7 @@ class HighScoreManager(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addHighScore(highScore: HighScore) {
|
fun addHighScore(highScore: HighScore) {
|
||||||
|
// Save to local high scores
|
||||||
val currentScores = getHighScores().toMutableList()
|
val currentScores = getHighScores().toMutableList()
|
||||||
currentScores.add(highScore)
|
currentScores.add(highScore)
|
||||||
|
|
||||||
|
@ -37,6 +39,9 @@ class HighScoreManager(private val context: Context) {
|
||||||
// Save to SharedPreferences
|
// Save to SharedPreferences
|
||||||
val json = gson.toJson(topScores)
|
val json = gson.toJson(topScores)
|
||||||
prefs.edit().putString(KEY_HIGHSCORES, json).apply()
|
prefs.edit().putString(KEY_HIGHSCORES, json).apply()
|
||||||
|
|
||||||
|
// Submit to Google Play Games leaderboard if signed in
|
||||||
|
submitScoreToGooglePlay(highScore.score)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isHighScore(score: Int): Boolean {
|
fun isHighScore(score: Int): Boolean {
|
||||||
|
@ -44,4 +49,26 @@ class HighScoreManager(private val context: Context) {
|
||||||
return currentScores.size < MAX_HIGHSCORES ||
|
return currentScores.size < MAX_HIGHSCORES ||
|
||||||
score > (currentScores.lastOrNull()?.score ?: 0)
|
score > (currentScores.lastOrNull()?.score ?: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Submit score to Google Play Games leaderboard
|
||||||
|
private fun submitScoreToGooglePlay(score: Int) {
|
||||||
|
// We don't have an activity here, so we can't submit the score directly
|
||||||
|
// The score will be submitted later when an activity is available
|
||||||
|
try {
|
||||||
|
googlePlayGamesManager.submitScore(score.toLong(), null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Log error but don't crash
|
||||||
|
android.util.Log.e("HighScoreManager", "Error submitting score to leaderboard", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to check if user is signed in to Google Play Games
|
||||||
|
fun isGooglePlaySignedIn(): Boolean {
|
||||||
|
return googlePlayGamesManager.isSignedIn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Google Play Games manager
|
||||||
|
fun getGooglePlayGamesManager(): GooglePlayGamesManager {
|
||||||
|
return googlePlayGamesManager
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -438,6 +438,20 @@
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:focusableInTouchMode="true" />
|
android:focusableInTouchMode="true" />
|
||||||
|
|
||||||
|
<!-- Leaderboard Button -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/leaderboardButton"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:text="Global Leaderboard"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:textAllCaps="false" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -407,6 +407,19 @@
|
||||||
android:fontFamily="sans-serif"
|
android:fontFamily="sans-serif"
|
||||||
android:textAllCaps="false" />
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/leaderboardButton"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:text="Global Leaderboard"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/statsButton"
|
android:id="@+id/statsButton"
|
||||||
android:layout_width="200dp"
|
android:layout_width="200dp"
|
||||||
|
|
|
@ -25,15 +25,37 @@
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/backButton"
|
android:id="@+id/backButton"
|
||||||
android:layout_width="200dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
android:text="@string/back"
|
android:text="@string/back"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:fontFamily="monospace"/>
|
android:fontFamily="monospace"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/leaderboardButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:text="Global Leaderboard"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:fontFamily="monospace"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Pixel Mint Drop</string>
|
<string name="app_name">Pixel Mint Drop</string>
|
||||||
|
<string name="app_id">630069234328</string>
|
||||||
<string name="game_over">game over</string>
|
<string name="game_over">game over</string>
|
||||||
<string name="score">score</string>
|
<string name="score">score</string>
|
||||||
<string name="level">level</string>
|
<string name="level">level</string>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue