From 4c0eae3e79ba9cbaff990f218578e6063b64cd6a Mon Sep 17 00:00:00 2001 From: davinci9196 Date: Fri, 22 Nov 2024 11:00:32 +0800 Subject: [PATCH] Avoid crashes on foldable screens --- .../src/main/AndroidManifest.xml | 7 +- .../microg/gms/games/ui/GamesUiFragment.kt | 217 +++++++++++------- .../microg/gms/games/ui/InGameUiActivity.kt | 17 +- 3 files changed, 149 insertions(+), 92 deletions(-) diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 255e3aaab7..1aa9e19d26 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -552,10 +552,11 @@ + android:launchMode="singleTask" + android:theme="@style/Theme.AppCompat.Dialog.Alert"> diff --git a/play-services-core/src/main/kotlin/org/microg/gms/games/ui/GamesUiFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/games/ui/GamesUiFragment.kt index a60ab660bb..1f25fb1b77 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/games/ui/GamesUiFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/games/ui/GamesUiFragment.kt @@ -6,8 +6,10 @@ package org.microg.gms.games.ui import android.accounts.Account +import android.accounts.AccountManager import android.app.Activity.RESULT_OK import android.app.Dialog +import android.content.Context import android.content.DialogInterface import android.content.Intent import android.os.Bundle @@ -36,6 +38,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.microg.gms.auth.AuthConstants import org.microg.gms.auth.AuthManager import org.microg.gms.auth.signin.SignInConfigurationService import org.microg.gms.common.Constants @@ -70,13 +73,29 @@ import org.microg.gms.games.snapshot.SnapshotsDataClient import org.microg.gms.people.PeopleManager import org.microg.gms.profile.ProfileManager -private const val TAG = "GamesUiFragment" +class GamesUiFragment : BottomSheetDialogFragment() { -class GamesUiFragment( - private val clientPackageName: String, - private val account: Account?, - private val callerIntent: Intent, -) : BottomSheetDialogFragment() { + companion object { + const val TAG = "GamesUiFragment" + private const val KEY_PACKAGE_NAME = "clientPackageName" + private const val KEY_ACCOUNT_KEY = "accountKey" + private const val KEY_CALLER_INTENT = "callerIntent" + + fun newInstance(clientPackageName: String, accountKey: String, intent: Intent): GamesUiFragment { + val fragment = GamesUiFragment() + val args = Bundle().apply { + putString(KEY_PACKAGE_NAME, clientPackageName) + putString(KEY_ACCOUNT_KEY, accountKey) + putParcelable(KEY_CALLER_INTENT, intent) + } + fragment.arguments = args + return fragment + } + } + + private lateinit var clientPackageName: String + private lateinit var callerIntent: Intent + private var account: Account? = null private var playerLogo: ImageView? = null private var uiTitle: TextView? = null @@ -91,51 +110,27 @@ class GamesUiFragment( private var currentAccount: Account? = null private var snapshotsAdapter: SnapshotsAdapter? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Log.d(TAG, "onCreate start") + kotlin.runCatching { + clientPackageName = arguments?.getString(KEY_PACKAGE_NAME)!! + callerIntent = arguments?.getParcelable(KEY_CALLER_INTENT)!! + account = AccountManager.get(context).accounts.filter { + it.type == AuthConstants.DEFAULT_ACCOUNT_TYPE && Integer.toHexString(it.name.hashCode()) == arguments?.getString(KEY_ACCOUNT_KEY) + }.getOrNull(0) + }.onFailure { + activity?.finish() + } + } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - ProfileManager.ensureInitialized(requireContext()) - lifecycleScope.launch { - currentAccount = - account ?: GamesConfigurationService.getDefaultAccount(requireContext(), clientPackageName) ?: SignInConfigurationService.getDefaultAccount( - requireContext(), clientPackageName - ) - - if (currentAccount == null) { - showErrorMsg(requireContext().getString(R.string.games_api_access_denied)) - return@launch - } - - withContext(Dispatchers.IO) { - PeopleManager.getOwnerAvatarBitmap( - requireContext(), currentAccount!!.name, false - ) - }?.also { - playerLogo?.setImageBitmap(it) - } - - val authResponse = withContext(Dispatchers.IO) { - AuthManager(context, currentAccount!!.name, clientPackageName, "$SERVICE_GAMES_LITE ${Scopes.DRIVE_APPFOLDER}").apply { isPermitted = true }.requestAuth(true) - } - var oauthToken: String? = null - if (authResponse.auth?.let { oauthToken = it } == null) { - showErrorMsg(requireContext().getString(R.string.games_achievements_empty_text)) - return@launch - } - - runCatching { - when (callerIntent.action) { - ACTION_VIEW_ACHIEVEMENTS -> loadLocalAchievements(oauthToken!!) - ACTION_VIEW_LEADERBOARDS -> loadLocalLeaderboards(oauthToken!!) - ACTION_VIEW_SNAPSHOTS -> loadSnapshots(oauthToken!!) - ACTION_VIEW_LEADERBOARDS_SCORES -> loadLocalLeaderboardScores(oauthToken!!) - else -> { - showErrorMsg("Not yet implemented") - } - } - }.onFailure { - Log.d(TAG, "show error: ", it) - activity?.finish() - } + Log.d(TAG, "onActivityCreated start ") + kotlin.runCatching { + loadData(requireContext()) + }.onFailure { + activity?.finish() } } @@ -190,7 +185,52 @@ class GamesUiFragment( errorView?.text = error } - private suspend fun loadSnapshots(oauthToken: String) { + private fun loadData(context: Context) { + ProfileManager.ensureInitialized(context) + lifecycleScope.launch { + currentAccount = account ?: GamesConfigurationService.getDefaultAccount(context, clientPackageName) ?: SignInConfigurationService.getDefaultAccount( + context, clientPackageName + ) + + if (currentAccount == null) { + showErrorMsg(context.getString(R.string.games_api_access_denied)) + return@launch + } + + withContext(Dispatchers.IO) { + PeopleManager.getOwnerAvatarBitmap( + context, currentAccount!!.name, false + ) + }?.also { + playerLogo?.setImageBitmap(it) + } + + val authResponse = withContext(Dispatchers.IO) { + AuthManager(context, currentAccount!!.name, clientPackageName, "$SERVICE_GAMES_LITE ${Scopes.DRIVE_APPFOLDER}").apply { isPermitted = true } + .requestAuth(true) + } + var oauthToken: String? = null + if (authResponse.auth?.let { oauthToken = it } == null) { + showErrorMsg(context.getString(R.string.games_achievements_empty_text)) + return@launch + } + + runCatching { + when (callerIntent.action) { + ACTION_VIEW_ACHIEVEMENTS -> loadLocalAchievements(context, oauthToken!!) + ACTION_VIEW_LEADERBOARDS -> loadLocalLeaderboards(context, oauthToken!!) + ACTION_VIEW_SNAPSHOTS -> loadSnapshots(context, oauthToken!!) + ACTION_VIEW_LEADERBOARDS_SCORES -> loadLocalLeaderboardScores(context, oauthToken!!) + else -> showErrorMsg("Not yet implemented") + } + }.onFailure { + Log.d(TAG, "show error: ", it) + activity?.finish() + } + } + } + + private suspend fun loadSnapshots(context: Context, oauthToken: String) { uiTitle?.text = callerIntent.getStringExtra(EXTRA_TITLE) refreshBtn?.visibility = View.VISIBLE refreshBtn?.setOnClickListener { @@ -198,10 +238,10 @@ class GamesUiFragment( loadingView?.visibility = View.VISIBLE lifecycleScope.launchWhenCreated { val snapshots = withContext(Dispatchers.IO) { - SnapshotsDataClient.get(requireContext()).loadSnapshotData(oauthToken) + SnapshotsDataClient.get(context).loadSnapshotData(oauthToken) } if (snapshots.isEmpty()) { - showErrorMsg(requireContext().getString(R.string.games_snapshot_empty_text)) + showErrorMsg(context.getString(R.string.games_snapshot_empty_text)) } else { snapshotsAdapter?.update(snapshots) } @@ -209,33 +249,44 @@ class GamesUiFragment( } } val snapshots = withContext(Dispatchers.IO) { - SnapshotsDataClient.get(requireContext()).loadSnapshotData(oauthToken) + SnapshotsDataClient.get(context).loadSnapshotData(oauthToken) } addSnapshotBtnDetail(snapshots) if (snapshots.isEmpty()) { - showErrorMsg(requireContext().getString(R.string.games_snapshot_empty_text)) + showErrorMsg(context.getString(R.string.games_snapshot_empty_text)) return } recyclerView?.apply { - layoutManager = LinearLayoutManager(requireContext()) + layoutManager = LinearLayoutManager(context) addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> errorView?.visibility = View.GONE loadingView?.visibility = View.GONE } - }?.adapter = snapshotsAdapter ?: SnapshotsAdapter(requireContext(), callerIntent, snapshots) { snapshot, i -> + }?.adapter = snapshotsAdapter ?: SnapshotsAdapter(context, callerIntent, snapshots) { snapshot, i -> lifecycleScope.launch { if (i == 0) { val intent = Intent() val snapshotMetadataEntity = SnapshotMetadataEntity( - null, null, snapshot.id, null, - snapshot.coverImage?.url, snapshot.title, snapshot.description, snapshot.lastModifiedMillis?.toLong() ?: 0, - 0, 1f, snapshot.title, false, 0, "" + null, + null, + snapshot.id, + null, + snapshot.coverImage?.url, + snapshot.title, + snapshot.description, + snapshot.lastModifiedMillis?.toLong() ?: 0, + 0, + 1f, + snapshot.title, + false, + 0, + "" ) intent.putExtra(EXTRA_SNAPSHOT_METADATA, snapshotMetadataEntity) activity?.setResult(RESULT_OK, intent) activity?.finish() } else { - AlertDialog.Builder(requireContext()).apply { + AlertDialog.Builder(context).apply { setTitle(getString(R.string.games_delete_snapshot_dialog_title)) setMessage(getString(R.string.games_delete_snapshot_dialog_message)) }.setNegativeButton(getString(R.string.games_delete_snapshot_dialog_cancel)) { dialog, _ -> @@ -243,11 +294,11 @@ class GamesUiFragment( }.setPositiveButton(getString(R.string.games_delete_snapshot_dialog_ok)) { dialog, _ -> dialog.dismiss() lifecycleScope.launchWhenCreated { - val snapshotData = SnapshotsDataClient.get(requireContext()).deleteSnapshotData(oauthToken, snapshot) + val snapshotData = SnapshotsDataClient.get(context).deleteSnapshotData(oauthToken, snapshot) if (snapshotData != null) { refreshBtn?.performClick() } else { - Toast.makeText(requireContext(), getString(R.string.games_delete_snapshot_error), Toast.LENGTH_SHORT).show() + Toast.makeText(context, getString(R.string.games_delete_snapshot_error), Toast.LENGTH_SHORT).show() } } }.show() @@ -274,28 +325,28 @@ class GamesUiFragment( } } - private suspend fun loadLocalLeaderboards(oauthToken: String) { - uiTitle?.text = requireContext().getString(R.string.games_leaderboard_list_title) + private suspend fun loadLocalLeaderboards(context: Context, oauthToken: String) { + uiTitle?.text = context.getString(R.string.games_leaderboard_list_title) val loadLeaderboards = withContext(Dispatchers.IO) { ArrayList().apply { var playerPageToken: String? = null do { - val response = LeaderboardsApiClient.requestAllLeaderboards(requireContext(), oauthToken, playerPageToken) + val response = LeaderboardsApiClient.requestAllLeaderboards(context, oauthToken, playerPageToken) addAll(response.items) playerPageToken = response.nextPageToken } while (!playerPageToken.isNullOrEmpty()) } } if (loadLeaderboards.isEmpty()) { - showErrorMsg(requireContext().getString(R.string.games_leaderboard_empty_text)) + showErrorMsg(context.getString(R.string.games_leaderboard_empty_text)) return } recyclerView?.apply { - layoutManager = LinearLayoutManager(requireContext()) + layoutManager = LinearLayoutManager(context) addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> loadingView?.visibility = View.GONE } - }?.adapter = LeaderboardsAdapter(requireContext(), loadLeaderboards) { leaderboard -> + }?.adapter = LeaderboardsAdapter(context, loadLeaderboards) { leaderboard -> val intent = Intent(ACTION_VIEW_LEADERBOARDS_SCORES) intent.setPackage(Constants.GMS_PACKAGE_NAME) intent.putExtra(EXTRA_GAME_PACKAGE_NAME, clientPackageName) @@ -305,28 +356,28 @@ class GamesUiFragment( } } - private suspend fun loadLocalLeaderboardScores(oauthToken: String) { + private suspend fun loadLocalLeaderboardScores(context: Context, oauthToken: String) { val leaderboardId = callerIntent.getStringExtra(EXTRA_LEADERBOARD_ID) val leaderboardScores = withContext(Dispatchers.IO) { ArrayList().apply { val response = LeaderboardsApiClient.requestLeaderboardScoresById( - requireContext(), oauthToken, leaderboardId!!, null + context, oauthToken, leaderboardId!!, null ) addAll(response.items) } } if (leaderboardScores.isEmpty()) { - showErrorMsg(requireContext().getString(R.string.games_leaderboard_empty_text)) + showErrorMsg(context.getString(R.string.games_leaderboard_empty_text)) return } val leaderboardDefinition = withContext(Dispatchers.IO) { - LeaderboardsApiClient.getLeaderboardById(requireContext(), oauthToken, leaderboardId!!) + LeaderboardsApiClient.getLeaderboardById(context, oauthToken, leaderboardId!!) } val leaderboardEntries = arrayListOf() leaderboardEntries.add(LeaderboardEntry(leaderboardDefinition.name, leaderboardDefinition.iconUrl)) leaderboardEntries.addAll(leaderboardScores) recyclerView?.apply { - layoutManager = LinearLayoutManager(requireContext()) + layoutManager = LinearLayoutManager(context) addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> loadingView?.visibility = View.GONE } @@ -340,24 +391,24 @@ class GamesUiFragment( } } }) - }?.adapter = LeaderboardScoresAdapter(requireContext(), leaderboardEntries) + }?.adapter = LeaderboardScoresAdapter(context, leaderboardEntries) } - private suspend fun loadLocalAchievements(oauthToken: String) { - uiTitle?.text = requireContext().getString(R.string.games_achievement_list_title) + private suspend fun loadLocalAchievements(context: Context, oauthToken: String) { + uiTitle?.text = context.getString(R.string.games_achievement_list_title) val allAchievements = withContext(Dispatchers.IO) { ArrayList().apply { val playerAchievements = ArrayList() var playerPageToken: String? = null do { - val response = AchievementsApiClient.requestPlayerAllAchievements(requireContext(), oauthToken, playerPageToken) + val response = AchievementsApiClient.requestPlayerAllAchievements(context, oauthToken, playerPageToken) playerAchievements.addAll(response.items) playerPageToken = response.nextPageToken } while (!playerPageToken.isNullOrEmpty()) var pageToken: String? = null do { - val response = AchievementsApiClient.requestGameAllAchievements(requireContext(), oauthToken, pageToken) + val response = AchievementsApiClient.requestGameAllAchievements(context, oauthToken, pageToken) response.items.forEach { item -> if (playerAchievements.any { it.id == item.id }) { item.initialState = getAchievementState(playerAchievements.find { it.id == item.id }?.achievementState) @@ -369,7 +420,7 @@ class GamesUiFragment( } } if (allAchievements.isEmpty()) { - showErrorMsg(requireContext().getString(R.string.games_achievements_empty_text)) + showErrorMsg(context.getString(R.string.games_achievements_empty_text)) return } val targetList = ArrayList() @@ -387,18 +438,18 @@ class GamesUiFragment( } } if (unlockList.isNotEmpty()) { - targetList.add(AchievementDefinition(requireContext().getString(R.string.games_achievement_unlocked_content_description), -1)) + targetList.add(AchievementDefinition(context.getString(R.string.games_achievement_unlocked_content_description), -1)) targetList.addAll(unlockList) } if (revealedList.isNotEmpty()) { - targetList.add(AchievementDefinition(requireContext().getString(R.string.games_achievement_locked_content_description), -1)) + targetList.add(AchievementDefinition(context.getString(R.string.games_achievement_locked_content_description), -1)) targetList.addAll(revealedList) } val inflatedView = contentVb?.inflate() inflatedView?.findViewById(R.id.achievements_counter_text)?.text = String.format("${unlockList.size} / ${unlockList.size + revealedList.size}") recyclerView?.apply { - layoutManager = LinearLayoutManager(requireContext()) + layoutManager = LinearLayoutManager(context) inflatedView?.id?.let { val layoutParams = layoutParams as RelativeLayout.LayoutParams layoutParams.addRule(RelativeLayout.BELOW, it) @@ -407,7 +458,7 @@ class GamesUiFragment( addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> loadingView?.visibility = View.GONE } - }?.adapter = AchievementsAdapter(requireContext(), targetList) + }?.adapter = AchievementsAdapter(context, targetList) } } \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/games/ui/InGameUiActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/games/ui/InGameUiActivity.kt index 9c639b2c1e..8a576b5a6c 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/games/ui/InGameUiActivity.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/games/ui/InGameUiActivity.kt @@ -5,12 +5,11 @@ package org.microg.gms.games.ui -import android.accounts.AccountManager +import android.annotation.SuppressLint import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity import com.google.android.gms.R -import org.microg.gms.auth.AuthConstants import org.microg.gms.games.EXTRA_ACCOUNT_KEY import org.microg.gms.games.EXTRA_GAME_PACKAGE_NAME @@ -27,6 +26,7 @@ class InGameUiActivity : AppCompatActivity() { intent.getStringExtra(EXTRA_ACCOUNT_KEY) }.getOrNull() + @SuppressLint("CommitTransaction") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setTheme(R.style.ThemeTranslucentCommon) @@ -36,10 +36,15 @@ class InGameUiActivity : AppCompatActivity() { finish() return } - val account = AccountManager.get(this).accounts.filter { - it.type == AuthConstants.DEFAULT_ACCOUNT_TYPE && Integer.toHexString(it.name.hashCode()) == accountKey - }.getOrNull(0) - GamesUiFragment(clientPackageName!!, account, intent).show(supportFragmentManager, "GamesUiFragment") + + val fragment = supportFragmentManager.findFragmentByTag(GamesUiFragment.TAG) + if (fragment == null) { + Log.d(TAG, "supportFragmentManager show") + val gamesUiFragment = GamesUiFragment.newInstance(clientPackageName!!, accountKey!!, intent) + supportFragmentManager.beginTransaction() + .add(gamesUiFragment, GamesUiFragment.TAG) + .commitAllowingStateLoss() + } } } \ No newline at end of file