Skip to content
Merged
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
2 changes: 2 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ kotlin {
implementation(libs.liquid)
implementation(libs.jetbrains.compose.material.icons.extended)

implementation(libs.touchlab.kermit)

implementation(compose.runtime)
implementation(compose.foundation)
implementation(libs.jetbrains.compose.material3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,26 @@ import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.koin.android.ext.android.get
import org.koin.android.ext.koin.androidContext
import zed.rainxch.core.data.services.PackageEventReceiver
import zed.rainxch.core.data.services.UpdateScheduler
import zed.rainxch.core.domain.model.InstallSource
import zed.rainxch.core.domain.model.InstalledApp
import zed.rainxch.core.domain.repository.InstalledAppsRepository
import zed.rainxch.core.domain.repository.ThemesRepository
import zed.rainxch.core.domain.system.PackageMonitor
import zed.rainxch.githubstore.app.di.initKoin

class GithubStoreApp : Application() {
private var packageEventReceiver: PackageEventReceiver? = null
private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

override fun onCreate() {
super.onCreate()
Expand All @@ -25,6 +35,7 @@ class GithubStoreApp : Application() {
createNotificationChannels()
registerPackageEventReceiver()
scheduleBackgroundUpdateChecks()
registerSelfAsInstalledApp()
}

private fun createNotificationChannels() {
Expand Down Expand Up @@ -70,10 +81,86 @@ class GithubStoreApp : Application() {
}

private fun scheduleBackgroundUpdateChecks() {
UpdateScheduler.schedule(context = this)
appScope.launch {
try {
val intervalHours = get<ThemesRepository>().getUpdateCheckInterval().first()
UpdateScheduler.schedule(
context = this@GithubStoreApp,
intervalHours = intervalHours,
)
} catch (e: Exception) {
Logger.e(e) { "Failed to schedule background update checks" }
}
}
}

private fun registerSelfAsInstalledApp() {
appScope.launch {
try {
val repo = get<InstalledAppsRepository>()
val selfPackageName = packageName
val existing = repo.getAppByPackage(selfPackageName)

if (existing != null) return@launch

val packageMonitor = get<PackageMonitor>()
val systemInfo = packageMonitor.getInstalledPackageInfo(selfPackageName)
if (systemInfo == null) {
Logger.w { "GithubStoreApp: Skip self-registration, package info missing for $selfPackageName" }
return@launch
}

val now = System.currentTimeMillis()
val versionName = systemInfo.versionName
val versionCode = systemInfo.versionCode

val selfApp =
InstalledApp(
packageName = selfPackageName,
repoId = SELF_REPO_ID,
repoName = SELF_REPO_NAME,
repoOwner = SELF_REPO_OWNER,
repoOwnerAvatarUrl = SELF_AVATAR_URL,
repoDescription = "A cross-platform app store for GitHub releases",
primaryLanguage = "Kotlin",
repoUrl = "https://github.com/$SELF_REPO_OWNER/$SELF_REPO_NAME",
installedVersion = versionName,
installedAssetName = null,
installedAssetUrl = null,
latestVersion = null,
latestAssetName = null,
latestAssetUrl = null,
latestAssetSize = null,
appName = "GitHub Store",
installSource = InstallSource.THIS_APP,
installedAt = now,
lastCheckedAt = 0L,
lastUpdatedAt = now,
isUpdateAvailable = false,
updateCheckEnabled = true,
releaseNotes = null,
systemArchitecture = "",
fileExtension = "apk",
isPendingInstall = false,
installedVersionName = versionName,
installedVersionCode = versionCode,
)

repo.saveInstalledApp(selfApp)
Logger.i("GitHub Store App: App added")
} catch (e: Exception) {
Logger.e(e) { "GitHub Store App: Failed to register self as installed app" }
}
}
}

companion object {
private const val SELF_REPO_ID = 1101281251L
private const val SELF_REPO_OWNER = "OpenHub-Store"
private const val SELF_REPO_NAME = "GitHub-Store"
private const val SELF_AVATAR_URL =
@Suppress("ktlint:standard:max-line-length")
"https://raw.githubusercontent.com/OpenHub-Store/GitHub-Store/refs/heads/main/media-resources/app_icon.png"
const val UPDATES_CHANNEL_ID = "app_updates"
const val UPDATE_SERVICE_CHANNEL_ID = "update_service"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import zed.rainxch.core.data.utils.AndroidBrowserHelper
import zed.rainxch.core.data.utils.AndroidClipboardHelper
import zed.rainxch.core.data.utils.AndroidShareManager
import zed.rainxch.core.domain.network.Downloader
import zed.rainxch.core.data.services.AndroidUpdateScheduleManager
import zed.rainxch.core.domain.system.Installer
import zed.rainxch.core.domain.system.InstallerStatusProvider
import zed.rainxch.core.domain.system.PackageMonitor
import zed.rainxch.core.domain.system.UpdateScheduleManager
import zed.rainxch.core.domain.utils.AppLauncher
import zed.rainxch.core.domain.utils.BrowserHelper
import zed.rainxch.core.domain.utils.ClipboardHelper
Expand Down Expand Up @@ -122,4 +124,10 @@ actual val corePlatformModule = module {
)
}

single<UpdateScheduleManager> {
AndroidUpdateScheduleManager(
context = androidContext()
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package zed.rainxch.core.data.services

import android.content.Context
import zed.rainxch.core.domain.system.UpdateScheduleManager

class AndroidUpdateScheduleManager(
private val context: Context,
) : UpdateScheduleManager {
override fun reschedule(intervalHours: Long) {
UpdateScheduler.reschedule(context, intervalHours)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,43 @@ object UpdateScheduler {
Logger.i { "UpdateScheduler: Scheduled periodic update check every ${intervalHours}h + immediate check" }
}

/**
* Force-reschedules the periodic update check with a new interval.
* Uses UPDATE policy to replace the existing schedule immediately.
* Call this when the user changes the update check interval in settings.
*/
fun reschedule(
context: Context,
intervalHours: Long,
) {
val constraints =
Constraints
.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()

val request =
PeriodicWorkRequestBuilder<UpdateCheckWorker>(
repeatInterval = intervalHours,
repeatIntervalTimeUnit = TimeUnit.HOURS,
).setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
30,
TimeUnit.MINUTES,
).build()

WorkManager
.getInstance(context)
.enqueueUniquePeriodicWork(
uniqueWorkName = UpdateCheckWorker.WORK_NAME,
existingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.UPDATE,
request = request,
)

Logger.i { "UpdateScheduler: Rescheduled periodic update check to every ${intervalHours}h" }
}

/**
* Enqueues a one-time [AutoUpdateWorker] to download and silently install
* all available updates via Shizuku. Uses KEEP policy to avoid duplicate runs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
Expand All @@ -22,6 +23,7 @@ class ThemesRepositoryImpl(
private val AUTO_DETECT_CLIPBOARD_KEY = booleanPreferencesKey("auto_detect_clipboard_links")
private val INSTALLER_TYPE_KEY = stringPreferencesKey("installer_type")
private val AUTO_UPDATE_KEY = booleanPreferencesKey("auto_update_enabled")
private val UPDATE_CHECK_INTERVAL_KEY = longPreferencesKey("update_check_interval_hours")

override fun getThemeColor(): Flow<AppTheme> =
preferences.data.map { prefs ->
Expand Down Expand Up @@ -107,4 +109,19 @@ class ThemesRepositoryImpl(
prefs[AUTO_UPDATE_KEY] = enabled
}
}

override fun getUpdateCheckInterval(): Flow<Long> =
preferences.data.map { prefs ->
prefs[UPDATE_CHECK_INTERVAL_KEY] ?: DEFAULT_UPDATE_CHECK_INTERVAL_HOURS
}

override suspend fun setUpdateCheckInterval(hours: Long) {
preferences.edit { prefs ->
prefs[UPDATE_CHECK_INTERVAL_KEY] = hours
}
}

companion object {
const val DEFAULT_UPDATE_CHECK_INTERVAL_HOURS = 6L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import zed.rainxch.core.data.services.DesktopFileLocationsProvider
import zed.rainxch.core.data.services.DesktopInstaller
import zed.rainxch.core.data.services.DesktopLocalizationManager
import zed.rainxch.core.data.services.DesktopPackageMonitor
import zed.rainxch.core.data.services.DesktopUpdateScheduleManager
import zed.rainxch.core.data.services.FileLocationsProvider
import zed.rainxch.core.domain.system.Installer
import zed.rainxch.core.domain.system.InstallerStatusProvider
import zed.rainxch.core.domain.system.UpdateScheduleManager
import zed.rainxch.core.data.services.LocalizationManager
import zed.rainxch.core.data.services.DesktopInstallerStatusProvider
import zed.rainxch.core.data.utils.DesktopShareManager
Expand Down Expand Up @@ -92,4 +94,8 @@ actual val corePlatformModule = module {
single<InstallerStatusProvider> {
DesktopInstallerStatusProvider()
}

single<UpdateScheduleManager> {
DesktopUpdateScheduleManager()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package zed.rainxch.core.data.services

import zed.rainxch.core.domain.system.UpdateScheduleManager

/**
* No-op implementation for Desktop — WorkManager is Android-only.
*/
class DesktopUpdateScheduleManager : UpdateScheduleManager {
override fun reschedule(intervalHours: Long) {
// No background scheduler on Desktop
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ interface ThemesRepository {
suspend fun setInstallerType(type: InstallerType)
fun getAutoUpdateEnabled(): Flow<Boolean>
suspend fun setAutoUpdateEnabled(enabled: Boolean)
fun getUpdateCheckInterval(): Flow<Long>
suspend fun setUpdateCheckInterval(hours: Long)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package zed.rainxch.core.domain.system

/**
* Abstraction for rescheduling background update checks.
* Android implementation delegates to WorkManager; Desktop is a no-op.
*/
interface UpdateScheduleManager {
/**
* Reschedules the periodic update check with a new interval.
* Takes effect immediately (replaces existing schedule).
*/
fun reschedule(intervalHours: Long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -521,4 +521,11 @@
<string name="auto_update_title">تحديث التطبيقات تلقائيًا</string>
<string name="auto_update_description">تنزيل التحديثات وتثبيتها تلقائيًا في الخلفية عبر Shizuku</string>

<string name="section_updates">التحديثات</string>
<string name="update_check_interval_title">فترة التحقق من التحديثات</string>
<string name="update_check_interval_description">عدد مرات التحقق من تحديثات التطبيق في الخلفية</string>
<string name="interval_3h">٣ ساعات</string>
<string name="interval_6h">٦ ساعات</string>
<string name="interval_12h">١٢ ساعة</string>
<string name="interval_24h">٢٤ ساعة</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -520,4 +520,11 @@
<string name="auto_update_title">স্বয়ংক্রিয়ভাবে অ্যাপ আপডেট করুন</string>
<string name="auto_update_description">Shizuku এর মাধ্যমে ব্যাকগ্রাউন্ডে স্বয়ংক্রিয়ভাবে আপডেট ডাউনলোড এবং ইনস্টল করুন</string>

<string name="section_updates">আপডেট</string>
<string name="update_check_interval_title">আপডেট চেক করার ব্যবধান</string>
<string name="update_check_interval_description">ব্যাকগ্রাউন্ডে কতক্ষণ পর পর অ্যাপ আপডেট খোঁজা হবে</string>
<string name="interval_3h">৩ঘ</string>
<string name="interval_6h">৬ঘ</string>
<string name="interval_12h">১২ঘ</string>
<string name="interval_24h">২৪ঘ</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -481,4 +481,11 @@
<string name="auto_update_title">Actualizar apps automáticamente</string>
<string name="auto_update_description">Descargar e instalar actualizaciones en segundo plano a través de Shizuku</string>

<string name="section_updates">Actualizaciones</string>
<string name="update_check_interval_title">Intervalo de verificación</string>
<string name="update_check_interval_description">Con qué frecuencia buscar actualizaciones en segundo plano</string>
<string name="interval_3h">3h</string>
<string name="interval_6h">6h</string>
<string name="interval_12h">12h</string>
<string name="interval_24h">24h</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -482,4 +482,11 @@
<string name="auto_update_title">Mise à jour automatique</string>
<string name="auto_update_description">Télécharger et installer automatiquement les mises à jour en arrière-plan via Shizuku</string>

<string name="section_updates">Mises à jour</string>
<string name="update_check_interval_title">Intervalle de vérification</string>
<string name="update_check_interval_description">Fréquence de vérification des mises à jour en arrière-plan</string>
<string name="interval_3h">3h</string>
<string name="interval_6h">6h</string>
<string name="interval_12h">12h</string>
<string name="interval_24h">24h</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -520,4 +520,11 @@
<string name="auto_update_title">ऐप्स ऑटो-अपडेट करें</string>
<string name="auto_update_description">Shizuku के माध्यम से पृष्ठभूमि में स्वचालित रूप से अपडेट डाउनलोड और इंस्टॉल करें</string>

<string name="section_updates">अपडेट</string>
<string name="update_check_interval_title">अपडेट जाँच अंतराल</string>
<string name="update_check_interval_description">पृष्ठभूमि में ऐप अपडेट कितनी बार जाँचें</string>
<string name="interval_3h">3घ</string>
<string name="interval_6h">6घ</string>
<string name="interval_12h">12घ</string>
<string name="interval_24h">24घ</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -520,4 +520,11 @@
<string name="auto_update_title">Aggiornamento automatico</string>
<string name="auto_update_description">Scarica e installa automaticamente gli aggiornamenti in background tramite Shizuku</string>

<string name="section_updates">Aggiornamenti</string>
<string name="update_check_interval_title">Intervallo di controllo</string>
<string name="update_check_interval_description">Ogni quanto verificare gli aggiornamenti in background</string>
<string name="interval_3h">3h</string>
<string name="interval_6h">6h</string>
<string name="interval_12h">12h</string>
<string name="interval_24h">24h</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -482,4 +482,11 @@
<string name="auto_update_title">アプリを自動更新</string>
<string name="auto_update_description">Shizukuを使用してバックグラウンドで自動的にアップデートをダウンロードしてインストール</string>

<string name="section_updates">アップデート</string>
<string name="update_check_interval_title">アップデート確認間隔</string>
<string name="update_check_interval_description">バックグラウンドでアプリのアップデートを確認する頻度</string>
<string name="interval_3h">3時間</string>
<string name="interval_6h">6時間</string>
<string name="interval_12h">12時間</string>
<string name="interval_24h">24時間</string>
</resources>
Loading