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
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
15 changes: 15 additions & 0 deletions app/src/main/java/app/gamenative/PluviaApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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
Expand All @@ -15,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 @@ -33,6 +36,9 @@ typealias NavChangedListener = NavController.OnDestinationChangedListener
@HiltAndroidApp
class PluviaApp : SplitCompatApplication() {

@Inject
lateinit var httpClient: OkHttpClient

override fun onCreate() {
super.onCreate()

Expand Down Expand Up @@ -92,6 +98,15 @@ class PluviaApp : SplitCompatApplication() {
} 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
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,
)
1 change: 1 addition & 0 deletions app/src/main/java/app/gamenative/data/GameSource.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ package app.gamenative.data

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

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

/**
* Data class for the Library list
Expand All @@ -14,7 +15,7 @@ data class LibraryItem(
val gameSource: GameSource = GameSource.STEAM,
) {
val clientIconUrl: String
get() = Constants.Library.ICON_URL + "$gameId/$iconHash.ico"
get() = GameManagerService.getIconImage(this)

/**
* Helper property to get the game ID as an integer
Expand Down
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
}
21 changes: 21 additions & 0 deletions app/src/main/java/app/gamenative/db/converters/GOGConverter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package app.gamenative.db.converters

import androidx.room.TypeConverter
import kotlinx.serialization.json.Json

class GOGConverter {

@TypeConverter
fun fromStringList(value: List<String>): String {
return Json.encodeToString(value)
}

@TypeConverter
fun toStringList(value: String): List<String> {
return try {
Json.decodeFromString<List<String>>(value)
} catch (e: Exception) {
emptyList()
}
}
}
75 changes: 75 additions & 0 deletions app/src/main/java/app/gamenative/db/dao/GOGGameDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package app.gamenative.db.dao

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import app.gamenative.data.GOGGame
import kotlinx.coroutines.flow.Flow

@Dao
interface GOGGameDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(game: GOGGame)

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(games: List<GOGGame>)

@Update
suspend fun update(game: GOGGame)

@Delete
suspend fun delete(game: GOGGame)

@Query("DELETE FROM gog_games WHERE id = :gameId")
suspend fun deleteById(gameId: String)

@Query("SELECT * FROM gog_games WHERE id = :gameId")
suspend fun getById(gameId: String): GOGGame?

@Query("SELECT * FROM gog_games ORDER BY title ASC")
fun getAll(): Flow<List<GOGGame>>

@Query("SELECT * FROM gog_games ORDER BY title ASC")
suspend fun getAllAsList(): List<GOGGame>

@Query("SELECT * FROM gog_games WHERE isInstalled = :isInstalled ORDER BY title ASC")
fun getByInstallStatus(isInstalled: Boolean): Flow<List<GOGGame>>

@Query("SELECT * FROM gog_games WHERE title LIKE '%' || :searchQuery || '%' ORDER BY title ASC")
fun searchByTitle(searchQuery: String): Flow<List<GOGGame>>

@Query("DELETE FROM gog_games")
suspend fun deleteAll()

@Query("SELECT COUNT(*) FROM gog_games")
fun getCount(): Flow<Int>

@Transaction
suspend fun replaceAll(games: List<GOGGame>) {
deleteAll()
insertAll(games)
}

@Transaction
suspend fun upsertPreservingInstallStatus(games: List<GOGGame>) {
games.forEach { newGame ->
val existingGame = getById(newGame.id)
if (existingGame != null) {
// Preserve installation status and path from existing game
val gameToInsert = newGame.copy(
isInstalled = existingGame.isInstalled,
installPath = existingGame.installPath,
)
insert(gameToInsert)
} else {
// New game, insert as-is
insert(newGame)
}
}
}
}
Loading