Skip to content

Commit f71aade

Browse files
committed
Deprecated desmume and lazily migrate in-game saves to melonds.
1 parent 1a1f6c4 commit f71aade

11 files changed

Lines changed: 279 additions & 25 deletions

File tree

lemuroid-app/src/main/java/com/swordfish/lemuroid/app/LemuroidApplicationModule.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
5454
import com.swordfish.lemuroid.lib.library.db.dao.GameSearchDao
5555
import com.swordfish.lemuroid.lib.library.db.dao.Migrations
5656
import com.swordfish.lemuroid.lib.library.metadata.GameMetadataProvider
57+
import com.swordfish.lemuroid.lib.migration.DesmumeMigrationHandler
5758
import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper
5859
import com.swordfish.lemuroid.lib.saves.SavesCoherencyEngine
5960
import com.swordfish.lemuroid.lib.saves.SavesManager
@@ -257,6 +258,7 @@ abstract class LemuroidApplicationModule {
257258
savesCoherencyEngine: SavesCoherencyEngine,
258259
directoriesManager: DirectoriesManager,
259260
biosManager: BiosManager,
261+
desmumeMigrationHandler: DesmumeMigrationHandler,
260262
) = GameLoader(
261263
lemuroidLibrary,
262264
statesManager,
@@ -266,6 +268,7 @@ abstract class LemuroidApplicationModule {
266268
savesCoherencyEngine,
267269
directoriesManager,
268270
biosManager,
271+
desmumeMigrationHandler,
269272
)
270273

271274
@Provides
@@ -289,7 +292,10 @@ abstract class LemuroidApplicationModule {
289292
@Provides
290293
@PerApp
291294
@JvmStatic
292-
fun coresSelection(sharedPreferences: Lazy<SharedPreferences>) = CoresSelection(sharedPreferences)
295+
fun coresSelection(
296+
sharedPreferences: Lazy<SharedPreferences>,
297+
desmumeMigrationHandler: DesmumeMigrationHandler,
298+
) = CoresSelection(sharedPreferences, desmumeMigrationHandler)
293299

294300
@Provides
295301
@PerApp
@@ -312,6 +318,11 @@ abstract class LemuroidApplicationModule {
312318
directoriesManager: DirectoriesManager,
313319
) = SaveSyncManagerImpl(context, directoriesManager)
314320

321+
@Provides
322+
@PerApp
323+
@JvmStatic
324+
fun desmumeMigrationHandler(directoriesManager: DirectoriesManager) = DesmumeMigrationHandler(directoriesManager)
325+
315326
@Provides
316327
@PerApp
317328
@JvmStatic

lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeScreen.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ fun HomeScreen(
3939
viewModel: HomeViewModel,
4040
onGameClick: (Game) -> Unit,
4141
onGameLongClick: (Game) -> Unit,
42+
onOpenCoreSelection: () -> Unit,
4243
) {
4344
val context = LocalContext.current
4445
val applicationContext = context.applicationContext
@@ -67,6 +68,7 @@ fun HomeScreen(
6768
state.value,
6869
onGameClick,
6970
onGameLongClick,
71+
onOpenCoreSelection,
7072
{
7173
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
7274
return@HomeScreen
@@ -85,6 +87,7 @@ private fun HomeScreen(
8587
state: HomeViewModel.UIState,
8688
onGameClicked: (Game) -> Unit,
8789
onGameLongClick: (Game) -> Unit,
90+
onOpenCoreSelection: () -> Unit,
8891
onEnableNotificationsClicked: () -> Unit,
8992
onEnableMicrophoneClicked: () -> Unit,
9093
onSetDirectoryClicked: () -> Unit,
@@ -93,7 +96,7 @@ private fun HomeScreen(
9396
modifier =
9497
modifier
9598
.verticalScroll(rememberScrollState())
96-
.padding(top = 16.dp),
99+
.padding(top = 16.dp, bottom = 16.dp),
97100
verticalArrangement = Arrangement.spacedBy(16.dp),
98101
) {
99102
AnimatedVisibility(state.showNoNotificationPermissionCard) {
@@ -121,6 +124,14 @@ private fun HomeScreen(
121124
onAction = onEnableMicrophoneClicked,
122125
)
123126
}
127+
AnimatedVisibility(state.showDesmumeDeprecatedCard) {
128+
HomeNotification(
129+
titleId = R.string.home_notification_desmume_deprecated_title,
130+
messageId = R.string.home_notification_desmume_deprecated_message,
131+
actionId = R.string.home_notification_desmume_deprecated_action,
132+
onAction = onOpenCoreSelection,
133+
)
134+
}
124135
HomeRow(
125136
stringResource(id = R.string.recent),
126137
state.recentGames,
@@ -206,7 +217,7 @@ private fun HomeNotification(
206217
) {
207218
Text(
208219
text = stringResource(titleId),
209-
style = MaterialTheme.typography.titleLarge,
220+
style = MaterialTheme.typography.titleMedium,
210221
)
211222
Text(
212223
text = stringResource(messageId),

lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/home/HomeViewModel.kt

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,29 @@ import com.swordfish.lemuroid.app.shared.library.PendingOperationsMonitor
1212
import com.swordfish.lemuroid.app.shared.settings.StorageFrameworkPickerLauncher
1313
import com.swordfish.lemuroid.common.coroutines.combine
1414
import com.swordfish.lemuroid.lib.core.CoresSelection
15-
import com.swordfish.lemuroid.lib.core.CoresSelection.SelectedCore
15+
import com.swordfish.lemuroid.lib.library.CoreID
16+
import com.swordfish.lemuroid.lib.library.SystemID
1617
import com.swordfish.lemuroid.lib.library.db.RetrogradeDatabase
1718
import com.swordfish.lemuroid.lib.library.db.entity.Game
1819
import kotlinx.coroutines.Dispatchers
20+
import kotlinx.coroutines.ExperimentalCoroutinesApi
1921
import kotlinx.coroutines.FlowPreview
2022
import kotlinx.coroutines.flow.Flow
2123
import kotlinx.coroutines.flow.MutableStateFlow
24+
import kotlinx.coroutines.flow.combine
2225
import kotlinx.coroutines.flow.debounce
26+
import kotlinx.coroutines.flow.distinctUntilChanged
27+
import kotlinx.coroutines.flow.flatMapLatest
28+
import kotlinx.coroutines.flow.flowOf
2329
import kotlinx.coroutines.flow.flowOn
30+
import kotlinx.coroutines.flow.map
2431
import kotlinx.coroutines.launch
2532

2633
@OptIn(FlowPreview::class)
2734
class HomeViewModel(
2835
appContext: Context,
2936
retrogradeDb: RetrogradeDatabase,
30-
private val coresSelection: CoresSelection
37+
private val coresSelection: CoresSelection,
3138
) : ViewModel() {
3239
companion object {
3340
const val CAROUSEL_MAX_ITEMS = 10
@@ -52,6 +59,7 @@ class HomeViewModel(
5259
val showNoNotificationPermissionCard: Boolean = false,
5360
val showNoMicrophonePermissionCard: Boolean = false,
5461
val showNoGamesCard: Boolean = false,
62+
val showDesmumeDeprecatedCard: Boolean = false,
5563
)
5664

5765
private val microphonePermissionEnabledState = MutableStateFlow(true)
@@ -101,21 +109,20 @@ class HomeViewModel(
101109
discoveryGames: List<Game>,
102110
indexInProgress: Boolean,
103111
notificationsPermissionEnabled: Boolean,
104-
microphonePermissionEnabled: Boolean,
105-
selectedCores: List<SelectedCore>,
112+
showMicrophoneCard: Boolean,
113+
showDesmumeWarning: Boolean,
106114
): UIState {
107115
val noGames = recentGames.isEmpty() && favoritesGames.isEmpty() && discoveryGames.isEmpty()
108-
val coreCanUseMicrophone = selectedCores
109-
.any { it.coreConfig.supportsMicrophone }
110116

111117
return UIState(
112118
favoritesGames = favoritesGames,
113119
recentGames = recentGames,
114120
discoveryGames = discoveryGames,
115121
indexInProgress = indexInProgress,
116122
showNoNotificationPermissionCard = !notificationsPermissionEnabled,
117-
showNoMicrophonePermissionCard = !microphonePermissionEnabled && coreCanUseMicrophone,
123+
showNoMicrophonePermissionCard = showMicrophoneCard,
118124
showNoGamesCard = noGames,
125+
showDesmumeDeprecatedCard = showDesmumeWarning,
119126
)
120127
}
121128

@@ -128,8 +135,8 @@ class HomeViewModel(
128135
discoveryGames(retrogradeDb),
129136
indexingInProgress(appContext),
130137
notificationsPermissionEnabledState,
131-
microphonePermissionEnabledState,
132-
coresSelection.getSelectedCores(),
138+
microphoneNotification(retrogradeDb),
139+
desmumeWarningNotification(),
133140
::buildViewState,
134141
)
135142

@@ -151,4 +158,40 @@ class HomeViewModel(
151158

152159
private fun favoritesGames(retrogradeDb: RetrogradeDatabase) =
153160
retrogradeDb.gameDao().selectFirstFavorites(CAROUSEL_MAX_ITEMS)
161+
162+
private fun dsGamesCount(retrogradeDb: RetrogradeDatabase): Flow<Int> {
163+
return retrogradeDb.gameDao().selectSystemsWithCount()
164+
.map { systems ->
165+
systems
166+
.firstOrNull { it.systemId == SystemID.NDS.dbname }
167+
?.count
168+
?: 0
169+
}
170+
.distinctUntilChanged()
171+
}
172+
173+
@OptIn(ExperimentalCoroutinesApi::class)
174+
private fun microphoneNotification(db: RetrogradeDatabase): Flow<Boolean> {
175+
return microphonePermissionEnabledState
176+
.flatMapLatest { isMicrophoneEnabled ->
177+
if (isMicrophoneEnabled) {
178+
flowOf(false)
179+
} else {
180+
combine(
181+
coresSelection.getSelectedCores(),
182+
dsGamesCount(db)
183+
) { cores, dsCount ->
184+
cores.any { it.coreConfig.supportsMicrophone } &&
185+
dsCount > 0
186+
}
187+
}
188+
.distinctUntilChanged()
189+
}
190+
}
191+
192+
private fun desmumeWarningNotification(): Flow<Boolean> {
193+
return coresSelection.getSelectedCores()
194+
.map { cores -> cores.any { it.coreConfig.coreID == CoreID.DESMUME } }
195+
.distinctUntilChanged()
196+
}
154197
}

lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/main/MainActivity.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ class MainActivity : RetrogradeComponentActivity(), BusyActivity {
202202
),
203203
onGameClick = onGameClick,
204204
onGameLongClick = onGameLongClick,
205+
onOpenCoreSelection = { navController.navigateToRoute(MainRoute.SETTINGS_CORES_SELECTION) },
205206
)
206207
}
207208
composable(MainRoute.FAVORITES) {

lemuroid-app/src/main/java/com/swordfish/lemuroid/app/mobile/feature/settings/coreselection/CoresSelectionScreen.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package com.swordfish.lemuroid.app.mobile.feature.settings.coreselection
22

33
import androidx.compose.foundation.layout.fillMaxWidth
4+
import androidx.compose.foundation.layout.padding
5+
import androidx.compose.material3.ElevatedCard
6+
import androidx.compose.material3.MaterialTheme
47
import androidx.compose.material3.Text
58
import androidx.compose.runtime.Composable
69
import androidx.compose.runtime.collectAsState
710
import androidx.compose.ui.Modifier
811
import androidx.compose.ui.platform.LocalContext
912
import androidx.compose.ui.res.stringResource
13+
import androidx.compose.ui.unit.dp
1014
import com.alorma.compose.settings.storage.memory.rememberMemoryIntSettingState
1115
import com.swordfish.lemuroid.app.utils.android.settings.LemuroidCardSettingsGroup
1216
import com.swordfish.lemuroid.app.utils.android.settings.LemuroidSettingsList
1317
import com.swordfish.lemuroid.app.utils.android.settings.LemuroidSettingsPage
18+
import com.swordfish.lemuroid.R
1419

1520
@Composable
1621
fun CoresSelectionScreen(
@@ -24,6 +29,17 @@ fun CoresSelectionScreen(
2429
val indexingInProgress = viewModel.indexingInProgress.collectAsState(false).value
2530

2631
LemuroidSettingsPage(modifier = modifier.fillMaxWidth()) {
32+
ElevatedCard(
33+
modifier = Modifier
34+
.fillMaxWidth()
35+
.padding(start = 16.dp, end = 16.dp),
36+
) {
37+
Text(
38+
modifier = Modifier.padding(8.dp),
39+
text = stringResource(R.string.settings_core_selection_ds_info),
40+
style = MaterialTheme.typography.bodyMedium,
41+
)
42+
}
2743
LemuroidCardSettingsGroup {
2844
cores.forEach { (system, core) ->
2945
val state = rememberMemoryIntSettingState(system.systemCoreConfigs.indexOf(core))

lemuroid-app/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252

5353
<string name="settings_title_open_cores_selection">Change Cores</string>
5454
<string name="settings_description_open_cores_selection">Select which core is associated to a system</string>
55+
<string name="settings_core_selection_ds_info">DeSmuME is deprecated and will be removed soon. Save inside the game, then switch to melonDS: in-game saves load there automatically, but save states stay on the core that created them.</string>
5556

5657
<string name="settings_title_display_bios_info">BIOS</string>
5758
<string name="settings_description_display_bios_info">Display detected and supported BIOS files</string>
@@ -108,6 +109,10 @@
108109
<string name="home_empty_message">Lemuroid needs to scan a directory containing ROM files.\nThis might take up to a few minutes.</string>
109110
<string name="home_empty_action">Select Directory</string>
110111

112+
<string name="home_notification_desmume_deprecated_title">DeSmuME retiring soon</string>
113+
<string name="home_notification_desmume_deprecated_message">Save in-game, switch to melonDS, and your progress will carry over. Save states stay tied to DeSmuME.</string>
114+
<string name="home_notification_desmume_deprecated_action">Switch to melonDS</string>
115+
111116
<string name="game_menu_save">Save</string>
112117
<string name="game_menu_load">Load</string>
113118
<string name="game_menu_restart">Restart</string>

retrograde-app-shared/src/main/java/com/swordfish/lemuroid/lib/core/CoresSelection.kt

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
package com.swordfish.lemuroid.lib.core
22

33
import android.content.SharedPreferences
4+
import androidx.core.content.edit
45
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
56
import com.swordfish.lemuroid.lib.library.CoreID
67
import com.swordfish.lemuroid.lib.library.GameSystem
78
import com.swordfish.lemuroid.lib.library.SystemCoreConfig
89
import com.swordfish.lemuroid.lib.library.SystemID
10+
import com.swordfish.lemuroid.lib.migration.DesmumeMigrationHandler
911
import dagger.Lazy
1012
import kotlinx.coroutines.Dispatchers
1113
import kotlinx.coroutines.flow.Flow
1214
import kotlinx.coroutines.flow.combine
15+
import kotlinx.coroutines.flow.emitAll
1316
import kotlinx.coroutines.flow.first
17+
import kotlinx.coroutines.flow.flow
1418
import kotlinx.coroutines.flow.flowOn
1519
import kotlinx.coroutines.flow.map
1620
import kotlinx.coroutines.withContext
1721

18-
class CoresSelection(private val sharedPreferencesFactory: Lazy<SharedPreferences>) {
22+
class CoresSelection(
23+
private val sharedPreferencesFactory: Lazy<SharedPreferences>,
24+
private val desmumeMigrationHandler: DesmumeMigrationHandler,
25+
) {
1926
private val sharedPreferences by lazy { sharedPreferencesFactory.get() }
2027

2128
private val flowSharedPreferences by lazy { FlowSharedPreferences(sharedPreferences) }
@@ -60,13 +67,36 @@ class CoresSelection(private val sharedPreferencesFactory: Lazy<SharedPreference
6067
}
6168

6269
private fun getSelectedCoreNameForSystem(system: GameSystem): Flow<String> {
63-
val preference =
64-
flowSharedPreferences.getString(
65-
computeSystemPreferenceKey(system.id),
66-
system.systemCoreConfigs.first().coreID.coreName,
70+
return flow {
71+
val preferenceKey = computeSystemPreferenceKey(system.id)
72+
val currentValue = flowSharedPreferences.sharedPreferences.getString(preferenceKey, null)
73+
var defaultValue = currentValue
74+
75+
if (currentValue == null) {
76+
defaultValue = getDefaultCoreForSystem(system)
77+
flowSharedPreferences.sharedPreferences.edit(true) {
78+
putString(preferenceKey, defaultValue)
79+
}
80+
}
81+
82+
val preference = flowSharedPreferences.getString(
83+
preferenceKey,
84+
defaultValue,
6785
)
68-
return preference.asFlow()
69-
.flowOn(Dispatchers.IO)
86+
emitAll(preference.asFlow())
87+
}.flowOn(Dispatchers.IO)
88+
}
89+
90+
// TODO Also get rid of this when desmume is gone
91+
private fun getDefaultCoreForSystem(system: GameSystem): String {
92+
if (system.id == SystemID.NDS) {
93+
return if (desmumeMigrationHandler.hasPendingDesmumeSaves()) {
94+
CoreID.DESMUME.coreName
95+
} else {
96+
CoreID.MELONDS.coreName
97+
}
98+
}
99+
return system.systemCoreConfigs.first().coreID.coreName
70100
}
71101

72102
companion object {

0 commit comments

Comments
 (0)