diff --git a/app/src/main/app/db/PluviaDatabase.kt b/app/src/main/app/db/PluviaDatabase.kt index cb118149e..5fbb90f6f 100644 --- a/app/src/main/app/db/PluviaDatabase.kt +++ b/app/src/main/app/db/PluviaDatabase.kt @@ -28,6 +28,8 @@ import com.winlator.cmod.feature.stores.steam.db.dao.SteamAppDao import com.winlator.cmod.feature.stores.steam.db.dao.SteamLicenseDao import com.winlator.cmod.app.db.download.DownloadRecord import com.winlator.cmod.app.db.download.DownloadRecordDao +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.gog.service.GOGService const val DATABASE_NAME = "pluvia_database" @@ -45,7 +47,7 @@ const val DATABASE_NAME = "pluvia_database" DownloadingAppInfo::class, DownloadRecord::class, ], - version = 8, + version = 9, exportSchema = false, ) @TypeConverters( @@ -91,7 +93,7 @@ abstract class PluviaDatabase : RoomDatabase() { context.applicationContext, PluviaDatabase::class.java, DATABASE_NAME, - ).addMigrations(MIGRATION_6_7, MIGRATION_7_8) + ).addMigrations(MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9) .fallbackToDestructiveMigration(true) .build() .also { instance = it } @@ -101,6 +103,24 @@ abstract class PluviaDatabase : RoomDatabase() { fun getInstance(): PluviaDatabase = instance ?: throw IllegalStateException("PluviaDatabase not initialized") + private val MIGRATION_8_9 = + object : Migration(8, 9) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE gog_games_new (id TEXT NOT NULL, title TEXT NOT NULL, slug TEXT NOT NULL, download_size INTEGER NOT NULL, install_size INTEGER NOT NULL, is_installed INTEGER NOT NULL, install_path TEXT NOT NULL, image_url TEXT NOT NULL, hero_image_url TEXT NOT NULL, icon_url TEXT NOT NULL, description TEXT NOT NULL, release_date TEXT NOT NULL, developer TEXT NOT NULL, publisher TEXT NOT NULL, genres TEXT NOT NULL, languages TEXT NOT NULL, last_played INTEGER NOT NULL, play_time INTEGER NOT NULL, type INTEGER NOT NULL, exclude INTEGER NOT NULL DEFAULT 0, user_id TEXT NOT NULL, categories TEXT NOT NULL, PRIMARY KEY(id, user_id))") + if (PrefManager.gogCurrentAccountId.isEmpty() || !GOGService.isRunning) { + db.execSQL("DROP TABLE gog_games") + db.execSQL("ALTER TABLE gog_games_new RENAME TO gog_games") + } else { + db.execSQL("ALTER TABLE gog_games ADD COLUMN user_id TEXT NOT NULL DEFAULT ${PrefManager.gogCurrentAccountId}") + db.execSQL("ALTER TABLE gog_games ADD COLUMN categories TEXT NOT NULL DEFAULT ''") + + db.execSQL("INSERT INTO gog_games_new SELECT * FROM gog_games") + db.execSQL("DROP TABLE gog_games") + db.execSQL("ALTER TABLE gog_games_new RENAME TO gog_games") + } + } + } + private val MIGRATION_7_8 = object : Migration(7, 8) { override fun migrate(db: SupportSQLiteDatabase) { diff --git a/app/src/main/feature/settings/stores/StoresFragment.kt b/app/src/main/feature/settings/stores/StoresFragment.kt index d4633c1d2..c39466abd 100644 --- a/app/src/main/feature/settings/stores/StoresFragment.kt +++ b/app/src/main/feature/settings/stores/StoresFragment.kt @@ -51,7 +51,10 @@ class StoresFragment : Fragment() { CoroutineScope(Dispatchers.Main).launch { val authResult = GOGAuthManager.authenticateWithCode(requireContext(), code) if (authResult.isSuccess) { - GOGService.start(requireContext()) + if (!GOGService.isRunning) + GOGService.start(requireContext()) + else + GOGService.refreshLibrary(requireContext()) } refresh() } @@ -115,6 +118,7 @@ class StoresFragment : Fragment() { ), ) { StoresScreen( + context = context, state = storeState, serverOptions = serverOptions, onSteamSignIn = { steamLoginLauncher.launch(Intent(requireContext(), SteamLoginActivity::class.java)) }, diff --git a/app/src/main/feature/settings/stores/StoresScreen.kt b/app/src/main/feature/settings/stores/StoresScreen.kt index 56e414afe..1e0daada3 100644 --- a/app/src/main/feature/settings/stores/StoresScreen.kt +++ b/app/src/main/feature/settings/stores/StoresScreen.kt @@ -1,4 +1,5 @@ package com.winlator.cmod.feature.settings +import android.content.Context import androidx.compose.animation.AnimatedContent import androidx.compose.animation.SizeTransform import androidx.compose.animation.core.FastOutSlowInEasing @@ -47,7 +48,6 @@ import androidx.compose.material.icons.outlined.KeyboardArrowDown import androidx.compose.material.icons.outlined.Language import androidx.compose.material.icons.outlined.Public import androidx.compose.material.icons.outlined.Speed -import androidx.compose.material.icons.outlined.Wifi import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.DropdownMenu @@ -78,10 +78,16 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import com.winlator.cmod.R +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager import com.winlator.cmod.shared.ui.focus.rememberSettingsContentNav import com.winlator.cmod.shared.ui.nav.LocalPaneNav import com.winlator.cmod.shared.ui.nav.paneNavItem import com.winlator.cmod.shared.ui.outlinedSwitchColors +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch // Palette private val BgDark = Color(0xFF11111C) @@ -116,6 +122,7 @@ data class StoreState( @Composable fun StoresScreen( + context: Context, state: StoreState, serverOptions: List>, onSteamSignIn: () -> Unit, @@ -165,28 +172,37 @@ fun StoresScreen( SectionLabel(stringResource(R.string.stores_accounts_connected_stores)) StoreCard( + context = context, name = stringResource(R.string.stores_accounts_steam_integration_title), icon = Icons.Outlined.Gamepad, accentColor = Color(0xFF66C0F4), isLoggedIn = state.isSteamLoggedIn, onSignIn = onSteamSignIn, onSignOut = onSteamSignOut, + onSwitch = null, + onGetAccounts = null ) StoreCard( + context = context, name = stringResource(R.string.preloader_platform_epic), icon = Icons.Outlined.Gamepad, accentColor = Color(0xFF8BAFD4), isLoggedIn = state.isEpicLoggedIn, onSignIn = onEpicSignIn, onSignOut = onEpicSignOut, + onSwitch = null, + onGetAccounts = null ) StoreCard( + context = context, name = stringResource(R.string.preloader_platform_gog), icon = Icons.Outlined.Gamepad, accentColor = Color(0xFFA855F7), isLoggedIn = state.isGogLoggedIn, onSignIn = onGogSignIn, onSignOut = onGogSignOut, + onSwitch = GOGService::switchAccount, + onGetAccounts = GOGAuthManager::getAccounts ) SectionLabel(stringResource(R.string.stores_accounts_download_settings), modifier = Modifier.padding(top = 8.dp)) @@ -352,13 +368,16 @@ private fun SignOutConfirmDialog( // Store card @Composable private fun StoreCard( + context: Context, name: String, icon: ImageVector, accentColor: Color, isLoggedIn: Boolean, onSignIn: () -> Unit, onSignOut: () -> Unit, -) { + onSwitch: ((String) -> Unit)?, + onGetAccounts: ((context: Context) -> Map?)?, + ) { var showSignOutDialog by remember { mutableStateOf(false) } if (showSignOutDialog) { SignOutConfirmDialog( @@ -479,6 +498,117 @@ private fun StoreCard( textColor = if (isLoggedIn) DangerRed else accentColor, onClick = if (isLoggedIn) ({ showSignOutDialog = true }) else onSignIn, ) + + if (!isLoggedIn || onGetAccounts == null) return@Box + Box(modifier = Modifier.padding(4.dp)) { + ActionButton( + label = stringResource(R.string.common_ui_add_more), + textColor = accentColor, + onClick = { + onSignIn() + }, + ) + } + + onGetAccounts.invoke(context)?.let { + if (it.isNotEmpty() && onSwitch != null) { + val usernames = it.map { itt -> itt.value } + SettingsDropDownMenu( + options = usernames, + selectedIndex = usernames.indexOf(it[PrefManager.gogCurrentAccountId]), + onOptionSelected = { selectedIndex -> + CoroutineScope(Dispatchers.IO).launch { + val userId = it.entries.firstOrNull { entry -> usernames[selectedIndex] == entry.value }?.key ?: return@launch + onSwitch(userId) + } + }, + ) + } + } + } + } +} + +@Composable +private fun SettingsDropDownMenu( + options: List, + selectedIndex: Int, + onOptionSelected: (Int) -> Unit, + accentColor: Color = Accent, +) { + var expanded by remember { mutableStateOf(false) } + val safeIndex = selectedIndex.coerceIn(0, (options.size - 1).coerceAtLeast(0)) + val selectedLabel = options.getOrNull(safeIndex) ?: "" + + Box { + Row( + modifier = + Modifier + .clip(RoundedCornerShape(8.dp)) + .background(Color(0xFF222232)) + .border(1.dp, accentColor.copy(alpha = 0.30f), RoundedCornerShape(8.dp)) + .paneNavItem( + cornerRadius = 8.dp, + onActivate = { if (options.isNotEmpty()) expanded = true }, + highlightColor = NavHighlight, + tapToSelect = true, + ).padding(horizontal = 10.dp, vertical = 7.dp) + .widthIn(max = 180.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = selectedLabel, + color = accentColor, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Icon( + imageVector = Icons.Outlined.KeyboardArrowDown, + contentDescription = null, + tint = accentColor, + modifier = Modifier.size(14.dp), + ) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + shape = RoundedCornerShape(8.dp), + containerColor = Color(0xFF24243B), + border = BorderStroke(1.dp, CardBorder), + modifier = Modifier.widthIn(max = 260.dp), + ) { + Column( + modifier = + Modifier + .heightIn(max = 260.dp) + .verticalScroll(rememberScrollState()), + ) { + options.forEachIndexed { index, label -> + val isSelected = index == safeIndex + DropdownMenuItem( + text = { + Text( + text = label, + color = if (isSelected) accentColor else TextPrimary, + fontSize = 13.sp, + fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal, + softWrap = true, + ) + }, + onClick = { + onOptionSelected(index) + expanded = false + }, + modifier = + Modifier.background( + if (isSelected) accentColor.copy(alpha = 0.08f) else Color.Transparent, + ), + ) + } + } } } } diff --git a/app/src/main/feature/stores/gog/data/GOGGame.kt b/app/src/main/feature/stores/gog/data/GOGGame.kt index e262a7a0a..130082297 100644 --- a/app/src/main/feature/stores/gog/data/GOGGame.kt +++ b/app/src/main/feature/stores/gog/data/GOGGame.kt @@ -8,9 +8,8 @@ import com.winlator.cmod.feature.stores.steam.enums.AppType * GOG Game entity for Room database * Represents a game from the GOG platform */ -@Entity(tableName = "gog_games") +@Entity(tableName = "gog_games", primaryKeys = ["id","user_id"]) data class GOGGame( - @PrimaryKey @ColumnInfo("id") val id: String, @ColumnInfo("title") @@ -51,6 +50,10 @@ data class GOGGame( val type: AppType = AppType.game, @ColumnInfo(name = "exclude", defaultValue = "0") val exclude: Boolean = false, + @ColumnInfo("user_id") + val userId: String, + @ColumnInfo("categories") + val categories: String, ) { companion object { const val GOG_IMAGE_BASE_URL = "https://images.gog.com/images" diff --git a/app/src/main/feature/stores/gog/db/dao/GOGGameDao.kt b/app/src/main/feature/stores/gog/db/dao/GOGGameDao.kt index 7570bd0a6..e806e615d 100644 --- a/app/src/main/feature/stores/gog/db/dao/GOGGameDao.kt +++ b/app/src/main/feature/stores/gog/db/dao/GOGGameDao.kt @@ -7,6 +7,7 @@ import androidx.room.Query import androidx.room.Transaction import androidx.room.Update import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.steam.utils.PrefManager import kotlinx.coroutines.flow.Flow /** @@ -26,32 +27,32 @@ interface GOGGameDao { @Delete suspend fun delete(game: GOGGame) - @Query("DELETE FROM gog_games WHERE id = :gameId") - suspend fun deleteById(gameId: String) + @Query("DELETE FROM gog_games WHERE id = :gameId AND user_id = :gogAccountId") + suspend fun deleteById(gameId: String, gogAccountId: String=PrefManager.gogCurrentAccountId) - @Query("SELECT * FROM gog_games WHERE id = :gameId") - suspend fun getById(gameId: String): GOGGame? + @Query("SELECT * FROM gog_games WHERE id = :gameId AND user_id = :gogAccountId") + suspend fun getById(gameId: String, gogAccountId: String=PrefManager.gogCurrentAccountId): GOGGame? - @Query("SELECT * FROM gog_games WHERE exclude = 0 ORDER BY title ASC") - fun getAll(): Flow> + @Query("SELECT * FROM gog_games WHERE exclude = 0 AND user_id = :gogAccountId ORDER BY title ASC") + fun getAll(gogAccountId: String=PrefManager.gogCurrentAccountId): Flow> - @Query("SELECT * FROM gog_games WHERE exclude = 0 ORDER BY title ASC") - suspend fun getAllAsList(): List + @Query("SELECT * FROM gog_games WHERE exclude = 0 AND user_id = :gogAccountId ORDER BY title ASC") + suspend fun getAllAsList(gogAccountId: String=PrefManager.gogCurrentAccountId): List - @Query("SELECT * FROM gog_games WHERE is_installed = :isInstalled AND exclude = 0 ORDER BY title ASC") - fun getByInstallStatus(isInstalled: Boolean): Flow> + @Query("SELECT * FROM gog_games WHERE is_installed = :isInstalled AND exclude = 0 AND user_id = :gogAccountId ORDER BY title ASC") + fun getByInstallStatus(isInstalled: Boolean, gogAccountId: String=PrefManager.gogCurrentAccountId): Flow> - @Query("SELECT * FROM gog_games WHERE exclude = 0 AND title LIKE '%' || :searchQuery || '%' ORDER BY title ASC") - fun searchByTitle(searchQuery: String): Flow> + @Query("SELECT * FROM gog_games WHERE exclude = 0 AND user_id = :gogAccountId AND title LIKE '%' || :searchQuery || '%' ORDER BY title ASC") + fun searchByTitle(searchQuery: String, gogAccountId: String=PrefManager.gogCurrentAccountId): Flow> - @Query("DELETE FROM gog_games WHERE is_installed = 0") - suspend fun deleteAllNonInstalledGames() + @Query("DELETE FROM gog_games WHERE is_installed = 0 AND user_id = :gogAccountId") + suspend fun deleteAllNonInstalledGames(gogAccountId: String=PrefManager.gogCurrentAccountId) - @Query("SELECT COUNT(*) FROM gog_games WHERE exclude = 0") - fun getCount(): Flow + @Query("SELECT COUNT(*) FROM gog_games WHERE exclude = 0 AND user_id = :gogAccountId") + fun getCount(gogAccountId: String=PrefManager.gogCurrentAccountId): Flow - @Query("SELECT id FROM gog_games") - suspend fun getAllGameIdsIncludingExcluded(): List + @Query("SELECT id FROM gog_games WHERE user_id = :gogAccountId") + suspend fun getAllGameIdsIncludingExcluded(gogAccountId: String=PrefManager.gogCurrentAccountId): List /** * Upsert GOG games while preserving install status and paths diff --git a/app/src/main/feature/stores/gog/service/GOGAuthManager.kt b/app/src/main/feature/stores/gog/service/GOGAuthManager.kt index 96d227017..e07f6d1ca 100644 --- a/app/src/main/feature/stores/gog/service/GOGAuthManager.kt +++ b/app/src/main/feature/stores/gog/service/GOGAuthManager.kt @@ -7,12 +7,11 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.withContext import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONObject import timber.log.Timber import java.io.File +import kotlinx.coroutines.runBlocking +import com.winlator.cmod.feature.stores.steam.utils.PrefManager /** * Manages GOG authentication and account operations. @@ -32,7 +31,67 @@ object GOGAuthManager { @JvmField internal var tokenUrl: String = "https://auth.gog.com/token" - fun getAuthConfigPath(context: Context): String = "${context.filesDir}/gog_auth.json" + fun updataAuthConfigFile(context: Context) { + val oldAuth = File("${context.filesDir}/gog_auth.json") + if (!oldAuth.isFile) return + val oldAuthContent = oldAuth.readText() + val oldAuthJson = JSONObject(oldAuthContent) + if (!oldAuthJson.has(GOGConstants.GOG_CLIENT_ID)) return + val credentialsJson = oldAuthJson.getJSONObject(GOGConstants.GOG_CLIENT_ID) + val userId = credentialsJson.optString("user_id") + val accessToken = credentialsJson.optString("access_token") + if (accessToken.isEmpty() || userId.isEmpty()) return + val username = runBlocking { getAccountUsername(accessToken) } + credentialsJson.put("username", username) + PrefManager.gogCurrentAccountId = userId + val newAuthPath = File("${context.filesDir}/gog_auth/gog_auth_${userId}.json") + newAuthPath.writeText(oldAuthJson.toString()) + oldAuth.delete() + } + + fun getAuthConfigPath(context: Context): String { + val gogAuthDirectory = "${context.filesDir}/gog_auth" + val profiles = File(gogAuthDirectory) + if (!profiles.isDirectory) + profiles.mkdirs() + updataAuthConfigFile(context) + val profile = PrefManager.gogCurrentAccountId + if (profile.isEmpty()) { + val accounts = runBlocking { getAccounts(context) } + if (!accounts.isNullOrEmpty()) { + PrefManager.gogCurrentAccountId = accounts.entries.last().key + } + } + return "${context.filesDir}/gog_auth/gog_auth_${profile}.json" + } + + + fun getAccounts(context: Context): Map? { + val accountsDirectory = File("${context.filesDir}/gog_auth/") + if (!accountsDirectory.isDirectory) return null + val accountFiles = accountsDirectory.listFiles() ?: return null + val results = mutableMapOf() + + accountFiles.forEach { file -> + val authContent = file.readText() + val authJson = JSONObject(authContent) + if (!authJson.has(GOGConstants.GOG_CLIENT_ID)) return@forEach + val credentialsJson = authJson.getJSONObject(GOGConstants.GOG_CLIENT_ID) + val userId = credentialsJson.optString("user_id") + var username = credentialsJson.optString("username") + if (userId.isEmpty()) return@forEach + if (username.isEmpty()) { + val accessToken = credentialsJson.optString("access_token") + username = runBlocking { getAccountUsername(accessToken) ?: "GOG User" } + if (username != "GOG User") { + credentialsJson.put("username", username) + file.writeText(authJson.toString()) + } + } + results[userId] = username + } + return results.toMap() + } fun hasStoredCredentials(context: Context): Boolean { val authFile = File(getAuthConfigPath(context)) @@ -60,17 +119,6 @@ object GOGAuthManager { if (actualCode.isEmpty()) { return Result.failure(Exception("Invalid authorization URL: no code parameter found")) } - - val authConfigPath = getAuthConfigPath(context) - - // Create auth config directory - val authFile = File(authConfigPath) - val authDir = authFile.parentFile - if (authDir != null && !authDir.exists()) { - authDir.mkdirs() - Timber.tag("GOG").d("Created auth config directory: ${authDir.absolutePath}") - } - // Exchange authorization code for tokens Timber.tag("GOG").d("Exchanging authorization code for tokens...") @@ -139,6 +187,7 @@ object GOGAuthManager { put( GOGConstants.GOG_CLIENT_ID, JSONObject().apply { + put("username", getAccountUsername(accessToken) ?: "ERROR") put("access_token", accessToken) put("refresh_token", refreshToken) put("user_id", userId) @@ -148,12 +197,14 @@ object GOGAuthManager { ) } + PrefManager.gogCurrentAccountId = userId + val authConfigPath = getAuthConfigPath(context) + val authFile = File(authConfigPath) withContext(Dispatchers.IO) { authFile.writeText(authData.toString(2)) } updateLoginStatus(context) Timber.tag("GOG").i("GOG authentication successful for user: $userId") - Result.success(credentials) } catch (e: Exception) { val errorMessage = e.message ?: e.javaClass.simpleName @@ -215,6 +266,34 @@ object GOGAuthManager { } } + suspend fun getAccountUsername(accessToken: String): String? { + val url = "https://embed.gog.com/userData.json" + + val request = + okhttp3.Request + .Builder() + .url(url) + .addHeader("Authorization", "Bearer ${accessToken}") + .addHeader("User-Agent", "WinNative/1.0") + .get() + .build() + + withContext(Dispatchers.IO) { + Net.http.newCall(request).execute() + }.use { response -> + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "Unknown error" + Timber.tag("GOG") + .e("Failed to get game token: HTTP ${response.code} - $errorBody") + return null + } + val responseBody = + response.body?.string() ?: return null + val json = JSONObject(responseBody) + return json.optString("username") + } + } + /** * Get game-specific credentials using the game's clientId and clientSecret. * This exchanges the Galaxy app's refresh token for a game-specific access token. @@ -238,6 +317,7 @@ object GOGAuthManager { // Read auth file val authContent = withContext(Dispatchers.IO) { authFile.readText() } val authJson = JSONObject(authContent) + val username = authJson.optString("username", "GOG User") if (authJson.has(clientId)) { val gameCredentials = authJson.getJSONObject(clientId) @@ -306,6 +386,7 @@ object GOGAuthManager { // Store the new game-specific credentials json.put("loginTime", System.currentTimeMillis() / 1000.0) + authJson.put("username", username) authJson.put(clientId, json) // Write updated auth file @@ -369,7 +450,8 @@ object GOGAuthManager { } else { true } - updateLoginStatus(context) + if (runBlocking { getAccounts(context) }?.isEmpty() ?: true) + updateLoginStatus(context) result } catch (e: Exception) { Timber.tag("GOG").e(e, "Failed to clear GOG credentials") diff --git a/app/src/main/feature/stores/gog/service/GOGManager.kt b/app/src/main/feature/stores/gog/service/GOGManager.kt index 4a1b7dee5..2f8e4b02c 100644 --- a/app/src/main/feature/stores/gog/service/GOGManager.kt +++ b/app/src/main/feature/stores/gog/service/GOGManager.kt @@ -28,6 +28,7 @@ import com.winlator.cmod.feature.stores.steam.utils.ContainerUtils import com.winlator.cmod.feature.stores.steam.utils.FileUtils import com.winlator.cmod.feature.stores.steam.utils.MarkerUtils import com.winlator.cmod.feature.stores.steam.utils.Net +import com.winlator.cmod.feature.stores.steam.utils.PrefManager import com.winlator.cmod.runtime.container.Container import com.winlator.cmod.runtime.container.ContainerManager import com.winlator.cmod.runtime.display.environment.components.GuestProgramLauncherComponent @@ -616,7 +617,7 @@ class GOGManager val gameDetails = result.getOrNull() if (gameDetails != null) { Timber.tag("GOG").d("Got Game Details for ID: $id") - val game = parseGameObject(gameDetails) + val game = parseGameObject(PrefManager.gogCurrentAccountId, gameDetails) if (game != null) { games.add(game) Timber.tag("GOG").d("Refreshed Game: ${game.title}") @@ -649,8 +650,7 @@ class GOGManager return@withContext Result.failure(e) } } - - private fun parseGameObject(parsedGame: ParsedGogGame): GOGGame? { + private fun parseGameObject(userId: String, parsedGame: ParsedGogGame): GOGGame? { val title = parsedGame.title val id = parsedGame.id val downloadSize = parsedGame.downloadSize @@ -683,6 +683,8 @@ class GOGManager installPath = "", lastPlayed = 0L, playTime = 0L, + userId = userId, + categories = "" ) } @@ -838,7 +840,7 @@ class GOGManager return Result.success(null) } - val game = parseGameObject(gameDetails) + val game = parseGameObject(PrefManager.gogCurrentAccountId, gameDetails) if (game == null) { Timber.tag("GOG").w("Skipping Invalid GOG App with id: $gameId") return Result.success(null) diff --git a/app/src/main/feature/stores/gog/service/GOGService.kt b/app/src/main/feature/stores/gog/service/GOGService.kt index b903a73d5..5bc390756 100644 --- a/app/src/main/feature/stores/gog/service/GOGService.kt +++ b/app/src/main/feature/stores/gog/service/GOGService.kt @@ -18,6 +18,7 @@ import com.winlator.cmod.feature.stores.steam.enums.Marker import com.winlator.cmod.feature.stores.steam.events.AndroidEvent import com.winlator.cmod.feature.stores.steam.utils.ContainerUtils import com.winlator.cmod.feature.stores.steam.utils.MarkerUtils +import com.winlator.cmod.feature.stores.steam.utils.PrefManager import com.winlator.cmod.feature.sync.google.GameSaveBackupManager.BackupResult import com.winlator.cmod.runtime.container.Container import com.winlator.cmod.runtime.system.SessionKeepAliveService @@ -430,6 +431,12 @@ class GOGService : Service() { fun clearStoredCredentials(context: Context): Boolean = GOGAuthManager.clearStoredCredentials(context) + fun switchAccount(userId: String) { + val instance = getInstance() ?: return + val context = instance.applicationContext + PrefManager.gogCurrentAccountId = userId + CoroutineScope(Dispatchers.IO).launch { instance.gogManager.refreshLibrary(context) } + } // Clears credentials, removes non-installed games, and stops the service. suspend fun logout(context: Context): Result { return withContext(Dispatchers.IO) { @@ -451,7 +458,14 @@ class GOGService : Service() { instance.gogManager.deleteAllNonInstalledGames() Timber.i("[GOGService] All non-installed GOG games removed from database") - stop() + val accounts = GOGAuthManager.getAccounts(context) + if (!accounts.isNullOrEmpty()) { + PrefManager.gogCurrentAccountId = accounts.entries.last().key + instance.gogManager.refreshLibrary(context) + } else { + PrefManager.gogCurrentAccountId = "" + stop() + } Timber.i("[GOGService] Logout completed successfully") Result.success(Unit) diff --git a/app/src/main/feature/stores/steam/utils/PrefManager.kt b/app/src/main/feature/stores/steam/utils/PrefManager.kt index e03f53821..b49462f92 100644 --- a/app/src/main/feature/stores/steam/utils/PrefManager.kt +++ b/app/src/main/feature/stores/steam/utils/PrefManager.kt @@ -324,6 +324,12 @@ object PrefManager { setString("gog_download_folder", value) } + var gogCurrentAccountId: String + get() = getString("gog_account_id", "") + set(value) { + setString("gog_account_id", value) + } + var chatServiceEnabled: Boolean get() = getBoolean("chat_service_enabled", true) set(value) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a78a56807..c87999717 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -146,6 +146,7 @@ Change Proceed Sign Out + Add More Name cannot be empty Error: %1$s 64-BIT