Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3e59b9d
Updated appId to be String and platform prefixed
unbelievableflavour Sep 4, 2025
ec96e31
Added container migration for backwards compatibility
unbelievableflavour Sep 4, 2025
bbe0823
Updated STEAM_ prefix to use gamesource enum
unbelievableflavour Sep 4, 2025
1b63595
Fixed crashes and memory leaks when relogging in
unbelievableflavour Sep 4, 2025
b42cbcd
Added separate account management screen to support multiple accounts
unbelievableflavour Sep 4, 2025
aaf95ed
Remove the generic logout button. Since it now moved into accounts.
unbelievableflavour Sep 4, 2025
9116db3
Let user remain in account management screen after sign out
unbelievableflavour Sep 4, 2025
72d7604
Use actual steam icon
unbelievableflavour Sep 4, 2025
dca574b
Linted code
unbelievableflavour Sep 5, 2025
66639bc
Added preview of account management screen preview (+ removed unused …
unbelievableflavour Sep 5, 2025
6d80b55
Preview matches theme
RadicalDog Sep 5, 2025
7a8b516
Update deprecated icons
RadicalDog Sep 5, 2025
cf99afd
Match navigation pattern with the rest of the app
RadicalDog Sep 5, 2025
ad9e5bd
Redraw UI on logOut
RadicalDog Sep 5, 2025
96a9de6
Swap data path references from requiring an instance of SteamService …
RadicalDog Sep 7, 2025
7951f8f
Save Steam ID to PrefManager so it is available without SteamService
RadicalDog Sep 7, 2025
f80d82c
Bring back sorting by install status before SteamService has an instance
RadicalDog Sep 7, 2025
84f3533
Don't delay for 10 seconds before allowing us in without a connection
RadicalDog Sep 7, 2025
8ad195a
Skip update check without internet, and avoid a crash
RadicalDog Sep 7, 2025
2027f81
Don't notify Steam without internet
RadicalDog Aug 1, 2025
d0542d0
Bypass Cloud dialog to launch offline
RadicalDog Sep 7, 2025
4878126
These messages offline are expected, and just Logcat pollution
RadicalDog Sep 7, 2025
34a1346
Extracted SteamSpecific code to platform specific game manager
unbelievableflavour Sep 9, 2025
e631311
Remove unneeded GOGDL (this was a whoopsie)
unbelievableflavour Sep 11, 2025
821476e
Addded docblock for isUpdatePending
unbelievableflavour Sep 11, 2025
4e1dbb5
Removed ?. in preferredSave?.ordinal
unbelievableflavour Sep 11, 2025
ce8be46
Merge branch 'master' into migrate-to-support-sources
RadicalDog Sep 11, 2025
08208df
Fix conflicts between int and string being used for containers, appId…
RadicalDog Sep 11, 2025
dceef8b
Updated the fallback profile picture image.
unbelievableflavour Sep 4, 2025
bcd810e
Extracted icon to gamemanagers
unbelievableflavour Sep 11, 2025
bd1015e
Added GOG support
unbelievableflavour Sep 10, 2025
22ac932
Service to manifest service
unbelievableflavour Sep 11, 2025
87e79c0
Added metadata support (images + release date)
unbelievableflavour Sep 11, 2025
832990c
Implement heroic-gogdl-android for debugging
unbelievableflavour Sep 12, 2025
3db62a9
Progressbar support, back to more default heroic-gogdl (v2 works)
unbelievableflavour Sep 12, 2025
3c272bd
Properly delete manifest info on deleting a game
unbelievableflavour Sep 12, 2025
139944c
Show progress screen when download is in progress
unbelievableflavour Sep 12, 2025
0b8bba9
gog POC single download + cancellable
unbelievableflavour Sep 12, 2025
3975ce8
Fixed info gogdl command
unbelievableflavour Sep 12, 2025
9e946ac
Retrieve developer from gamesdb
unbelievableflavour Sep 12, 2025
1b57148
Retrive download size async so GOG can do it as well
unbelievableflavour Sep 12, 2025
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
25 changes: 24 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ plugins {
alias(libs.plugins.jetbrains.serialization)
alias(libs.plugins.kotlinter)
alias(libs.plugins.ksp)
id("com.chaquo.python") version "15.0.1"
}

val keystorePropertiesFile = rootProject.file("app/keystores/keystore.properties")
Expand Down Expand Up @@ -76,8 +77,8 @@ android {
useSupportLibrary = true
}

// Restore the original ProGuard configuration
proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro",
)
Expand Down Expand Up @@ -170,8 +171,30 @@ android {
// }
}

chaquopy {
defaultConfig {
version = "3.11"
pip {
// Install GOGDL dependencies
install("requests")
// Use your Android-compatible fork instead of the original
// install("git+https://github.com/unbelievableflavour/heroic-gogdl-android.git@0.0.4")
}
}
sourceSets {
getByName("main") {
// Remove local Python source directory since we're using the external package
srcDir("src/main/python")
}
}
}

dependencies {
implementation(libs.material)

// Chrome Custom Tabs for OAuth
implementation("androidx.browser:browser:1.8.0")

// JavaSteaml
val localBuild = false // Change to 'true' needed when building JavaSteam manually
if (localBuild) {
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,22 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ui.screen.auth.GOGOAuthActivity"
android:exported="false"
android:theme="@style/Theme.Pluvia" />

<service
android:name=".service.SteamService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />

<service
android:name=".service.GOG.GOGService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
</application>

</manifest>
15 changes: 15 additions & 0 deletions app/src/main/java/app/gamenative/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import coil.memory.MemoryCache
import coil.request.CachePolicy
import app.gamenative.events.AndroidEvent
import app.gamenative.service.SteamService
import app.gamenative.service.GOG.GOGService
import app.gamenative.ui.PluviaMain
import app.gamenative.ui.enums.Orientation
import app.gamenative.utils.AnimatedPngDecoder
Expand Down Expand Up @@ -223,6 +224,11 @@ class MainActivity : ComponentActivity() {
Timber.i("Stopping Steam Service")
SteamService.stop()
}

if (GOGService.isRunning && !isChangingConfigurations) {
Timber.i("Stopping GOG Service")
GOGService.stop()
}
}

override fun onResume() {
Expand Down Expand Up @@ -254,6 +260,15 @@ class MainActivity : ComponentActivity() {
Timber.i("Stopping SteamService - no active operations")
SteamService.stop()
}

// stop GOGService only if no downloads or sync are in progress
if (!isChangingConfigurations &&
GOGService.isRunning &&
!GOGService.hasActiveOperations()
) {
Timber.i("Stopping GOGService - no active operations")
GOGService.stop()
}
}

// override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
Expand Down
27 changes: 27 additions & 0 deletions app/src/main/java/app/gamenative/PluviaApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package app.gamenative
import android.os.StrictMode
import androidx.navigation.NavController
import app.gamenative.events.EventDispatcher
import app.gamenative.service.DownloadService
import app.gamenative.service.GOG.GOGService
import app.gamenative.service.GameManagerService
import app.gamenative.utils.IntentLaunchManager
import com.google.android.play.core.splitcompat.SplitCompatApplication
import com.posthog.PersonProfiles
Expand All @@ -13,6 +16,8 @@ import com.winlator.widget.XServerView
import com.winlator.xenvironment.XEnvironment
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import okhttp3.OkHttpClient
import javax.inject.Inject

// Add PostHog imports
import com.posthog.android.PostHogAndroid
Expand All @@ -31,6 +36,9 @@ typealias NavChangedListener = NavController.OnDestinationChangedListener
@HiltAndroidApp
class PluviaApp : SplitCompatApplication() {

@Inject
lateinit var httpClient: OkHttpClient

override fun onCreate() {
super.onCreate()

Expand All @@ -54,6 +62,8 @@ class PluviaApp : SplitCompatApplication() {
// Init our datastore preferences.
PrefManager.init(this)

DownloadService.populateDownloadService(this)

// Clear any stale temporary config overrides from previous app sessions
try {
IntentLaunchManager.clearAllTemporaryOverrides()
Expand All @@ -80,6 +90,23 @@ class PluviaApp : SplitCompatApplication() {
Timber.e(e, "Failed to initialize Supabase client: ${e.message}")
e.printStackTrace()
}

// Initialize GameManagerService
try {
GameManagerService.initialize(this)
Timber.i("[PluviaApp]: GameManagerService initialized successfully")
} catch (e: Exception) {
Timber.e(e, "[PluviaApp]: Failed to initialize GameManagerService")
}

// Initialize GOG Service
try {
GOGService.initialize(this)
GOGService.setHttpClient(httpClient)
Timber.i("[PluviaApp]: GOG Service initialized successfully")
} catch (e: Exception) {
Timber.e(e, "[PluviaApp]: Failed to initialize GOG Service")
}
}

companion object {
Expand Down
14 changes: 11 additions & 3 deletions app/src/main/java/app/gamenative/PrefManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,6 @@ object PrefManager {
}
}

/**
* Get or Set the last known Persona State. See [EPersonaState]
*/
private val LIBRARY_FILTER = intPreferencesKey("library_filter")
var libraryFilter: EnumSet<AppFilter>
get() {
Expand All @@ -422,6 +419,9 @@ object PrefManager {
setPref(LIBRARY_FILTER, AppFilter.toFlags(value))
}

/**
* Get or Set the last known Persona State. See [EPersonaState]
*/
private val PERSONA_STATE = intPreferencesKey("persona_state")
var personaState: EPersonaState
get() {
Expand All @@ -432,6 +432,14 @@ object PrefManager {
setPref(PERSONA_STATE, value.code())
}

private val STEAM_USER_ACCOUNT_ID = intPreferencesKey("steam_user_account_id")
var steamUserAccountId: Int
get() = getPref(STEAM_USER_ACCOUNT_ID, 0)
set(value) {
setPref(STEAM_USER_ACCOUNT_ID, value)
}


private val ALLOWED_ORIENTATION = intPreferencesKey("allowed_orientation")
var allowedOrientation: EnumSet<Orientation>
get() {
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/app/gamenative/data/DownloadInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,31 @@ data class DownloadInfo(
val jobCount: Int = 1,
) {
private var downloadJob: Job? = null
private var progressMonitorJob: Job? = null
private val downloadProgressListeners = mutableListOf<((Float) -> Unit)>()
private val progresses: Array<Float> = Array(jobCount) { 0f }

private val weights = FloatArray(jobCount) { 1f } // ⇐ new
private var weightSum = jobCount.toFloat()

@Volatile
private var isCancelled = false

fun cancel() {
isCancelled = true
downloadJob?.cancel(CancellationException("Cancelled by user"))
progressMonitorJob?.cancel(CancellationException("Progress monitoring cancelled by user"))
}

fun isCancelled(): Boolean = isCancelled

fun setDownloadJob(job: Job) {
downloadJob = job
}

fun setProgressMonitorJob(job: Job) {
progressMonitorJob = job
}

fun getProgress(): Float {
var total = 0f
Expand Down
43 changes: 43 additions & 0 deletions app/src/main/java/app/gamenative/data/GOGGame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package app.gamenative.data

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "gog_games")
data class GOGGame(
@PrimaryKey
val id: String,
val title: String,
val slug: String,
val downloadSize: Long = 0,
val installSize: Long = 0,
val isInstalled: Boolean = false,
val installPath: String = "",
val imageUrl: String = "",
val iconUrl: String = "",
val description: String = "",
val releaseDate: String = "",
val developer: String = "",
val publisher: String = "",
val genres: List<String> = emptyList(),
val languages: List<String> = emptyList(),
val lastPlayed: Long = 0,
val playTime: Long = 0,
)

data class GOGCredentials(
val accessToken: String,
val refreshToken: String,
val userId: String,
val username: String,
)

data class GOGDownloadInfo(
val gameId: String,
val totalSize: Long,
val downloadedSize: Long = 0,
val progress: Float = 0f,
val isActive: Boolean = false,
val isPaused: Boolean = false,
val error: String? = null,
)
18 changes: 18 additions & 0 deletions app/src/main/java/app/gamenative/data/Game.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package app.gamenative.data

import app.gamenative.enums.AppType

/**
* Unified interface for all game types (Steam, GOG, etc.)
*/
interface Game {
val id: String
val name: String
val source: GameSource
val isInstalled: Boolean
val isShared: Boolean
val iconUrl: String
val appType: AppType

fun toLibraryItem(index: Int): LibraryItem
}
7 changes: 7 additions & 0 deletions app/src/main/java/app/gamenative/data/GameSource.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package app.gamenative.data

enum class GameSource {
STEAM,
GOG,
// Add new game sources here
}
13 changes: 11 additions & 2 deletions app/src/main/java/app/gamenative/data/LibraryItem.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package app.gamenative.data

import app.gamenative.Constants
import app.gamenative.service.GameManagerService

/**
* Data class for the Library list
*/
data class LibraryItem(
val index: Int = 0,
val appId: Int = 0,
val appId: String = "",
val name: String = "",
val iconHash: String = "",
val isShared: Boolean = false,
val gameSource: GameSource = GameSource.STEAM,
) {
val clientIconUrl: String
get() = Constants.Library.ICON_URL + "$appId/$iconHash.ico"
get() = GameManagerService.getIconImage(this)

/**
* Helper property to get the game ID as an integer
* Extracts the numeric part by removing the gameSource prefix
*/
val gameId: Int
get() = appId.removePrefix("${gameSource.name}_").toInt()
}
9 changes: 8 additions & 1 deletion app/src/main/java/app/gamenative/db/PluviaDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import app.gamenative.data.ChangeNumbers
import app.gamenative.data.Emoticon
import app.gamenative.data.FileChangeLists
import app.gamenative.data.FriendMessage
import app.gamenative.data.GOGGame
import app.gamenative.data.SteamApp
import app.gamenative.data.SteamFriend
import app.gamenative.data.SteamLicense
import app.gamenative.db.converters.AppConverter
import app.gamenative.db.converters.ByteArrayConverter
import app.gamenative.db.converters.FriendConverter
import app.gamenative.db.converters.GOGConverter
import app.gamenative.db.converters.LicenseConverter
import app.gamenative.db.converters.PathTypeConverter
import app.gamenative.db.converters.UserFileInfoListConverter
import app.gamenative.db.dao.ChangeNumbersDao
import app.gamenative.db.dao.EmoticonDao
import app.gamenative.db.dao.FileChangeListsDao
import app.gamenative.db.dao.FriendMessagesDao
import app.gamenative.db.dao.GOGGameDao
import app.gamenative.db.dao.SteamAppDao
import app.gamenative.db.dao.SteamFriendDao
import app.gamenative.db.dao.SteamLicenseDao
Expand All @@ -35,14 +38,16 @@ const val DATABASE_NAME = "pluvia.db"
FileChangeLists::class,
FriendMessage::class,
Emoticon::class,
GOGGame::class,
],
version = 3,
version = 4, // Increment version for new entity
exportSchema = false, // Should export once stable.
)
@TypeConverters(
AppConverter::class,
ByteArrayConverter::class,
FriendConverter::class,
GOGConverter::class,
LicenseConverter::class,
PathTypeConverter::class,
UserFileInfoListConverter::class,
Expand All @@ -62,4 +67,6 @@ abstract class PluviaDatabase : RoomDatabase() {
abstract fun friendMessagesDao(): FriendMessagesDao

abstract fun emoticonDao(): EmoticonDao

abstract fun gogGameDao(): GOGGameDao
}
Loading