Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,20 @@
<meta-data
android:name="com.samsung.android.multidisplay.keep_process_alive"
android:value="false"/>
<meta-data
android:name="android.allow_multiple_resumed_activities"
android:value="true" />

<meta-data
android:name="com.google.android.gms.games.APP_ID"
android:value="@string/play_games_app_id" />
<meta-data
android:name="com.google.android.gms.games.should_auto_sign_in"
android:value="true" />

</application>
</manifest>
<meta-data
android:name="android.allow_multiple_resumed_activities"
android:value="true" />

<meta-data
android:name="com.google.android.gms.games.APP_ID"
android:value="@string/play_games_app_id" />
<meta-data
android:name="com.google.android.gms.games.should_auto_sign_in"
android:value="false" />

<provider
android:name="com.google.android.gms.games.provider.PlayGamesInitProvider"
tools:node="remove" />

</application>
</manifest>
11 changes: 8 additions & 3 deletions app/src/main/app/shell/UnifiedActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8067,7 +8067,8 @@ class UnifiedActivity :
val scope = rememberCoroutineScope()
val context = LocalContext.current
var historyRefreshKey by remember { mutableStateOf(0) }
var historyLoading by remember { mutableStateOf(true) }
var historyRequested by remember { mutableStateOf(false) }
var historyLoading by remember { mutableStateOf(false) }
var historyEntries by remember { mutableStateOf<List<GameSaveBackupManager.BackupHistoryEntry>>(emptyList()) }
var entryPendingRestore by remember {
mutableStateOf<GameSaveBackupManager.BackupHistoryEntry?>(null)
Expand All @@ -8080,6 +8081,7 @@ class UnifiedActivity :
}

LaunchedEffect(gameSource, gameId, historyRefreshKey) {
if (!historyRequested) return@LaunchedEffect
historyLoading = true
historyEntries =
GameSaveBackupManager.listBackupHistory(
Expand All @@ -8094,7 +8096,7 @@ class UnifiedActivity :
// Auto-refresh the history list whenever a backup/restore finishes.
var wasWorking by remember { mutableStateOf(false) }
LaunchedEffect(isWorking) {
if (wasWorking && !isWorking) historyRefreshKey++
if (wasWorking && !isWorking && historyRequested) historyRefreshKey++
wasWorking = isWorking
}

Expand Down Expand Up @@ -8164,7 +8166,10 @@ class UnifiedActivity :
SaveHistorySection(
loading = historyLoading,
entries = historyEntries,
onRefresh = { historyRefreshKey++ },
onRefresh = {
historyRequested = true
historyRefreshKey++
},
onRestore = { entry -> entryPendingRestore = entry },
onRename = { entry -> entryPendingRename = entry },
onDelete = { entry -> entryPendingDelete = entry },
Expand Down
105 changes: 105 additions & 0 deletions app/src/main/feature/steamcloudsync/CloudSyncConflictDialog.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.winlator.cmod.feature.steamcloudsync
import android.app.Activity
import android.app.Dialog
import android.util.TypedValue
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.compose.material3.darkColorScheme
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.winlator.cmod.feature.sync.google.GameSaveBackupManager
import com.winlator.cmod.shared.theme.WinNativeTheme

data class CloudSyncConflictTimestamps(
val localTimestampLabel: String,
val cloudTimestampLabel: String,
)

/**
* Callback for the cloud-save conflict dialog. [keepBackup] reflects whether the
* user wanted the replaced side archived into Save History before the overwrite.
*/
fun interface CloudSyncConflictChoice {
fun onChoice(keepBackup: Boolean)
}

object CloudSyncConflictDialog {
@JvmStatic
fun show(
activity: Activity,
timestamps: CloudSyncConflictTimestamps,
onUseCloud: CloudSyncConflictChoice,
onUseLocal: CloudSyncConflictChoice,
) {
val dialog =
Dialog(activity, android.R.style.Theme_DeviceDefault_Dialog_NoActionBar).apply {
requestWindowFeature(Window.FEATURE_NO_TITLE)
setCancelable(false)
window?.apply {
setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT,
)
setBackgroundDrawableResource(android.R.color.transparent)
}
}

val composeView =
ComposeView(activity).apply {
layoutParams =
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
)
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
(activity as? ComponentActivity)?.let {
setViewTreeLifecycleOwner(it)
setViewTreeSavedStateRegistryOwner(it)
}
setContent {
WinNativeTheme(
colorScheme =
darkColorScheme(
primary = SteamCloudConflictBlue,
surface = SteamCloudConflictPanel,
background = SteamCloudConflictWindow,
onSurface = SteamCloudConflictText,
onBackground = SteamCloudConflictText,
),
) {
SteamCloudConflictDialogContent(
timestamps = timestamps,
initialKeepBackup = GameSaveBackupManager.isKeepReplacedBackupEnabled(activity),
onKeepBackupChanged = { enabled ->
GameSaveBackupManager.setKeepReplacedBackupEnabled(activity, enabled)
},
onUseCloud = { keepBackup ->
dialog.dismiss()
onUseCloud.onChoice(keepBackup)
},
onUseLocal = { keepBackup ->
dialog.dismiss()
onUseLocal.onChoice(keepBackup)
},
)
}
}
}

dialog.setContentView(composeView)
dialog.show()
dialog.window?.apply {
val dm = activity.resources.displayMetrics
val horizontalMarginPx =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, dm).toInt()
val maxDialogWidthPx =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 520f, dm).toInt()
val targetWidth = (dm.widthPixels - (horizontalMarginPx * 2)).coerceAtMost(maxDialogWidthPx)
setLayout(targetWidth, WindowManager.LayoutParams.WRAP_CONTENT)
}
}
}
Loading