Skip to content

Commit 54886f8

Browse files
feat: Add in-app update module
1 parent 3b7e936 commit 54886f8

26 files changed

Lines changed: 1252 additions & 2 deletions

File tree

InAppUpdate/build.gradle.kts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
plugins {
2+
id("com.android.library")
3+
alias(core.plugins.kotlin.android)
4+
alias(core.plugins.compose.compiler)
5+
kotlin("plugin.serialization")
6+
7+
}
8+
9+
val coreCompileSdk: Int by rootProject.extra
10+
val coreMinSdk: Int by rootProject.extra
11+
val javaVersion: JavaVersion by rootProject.extra
12+
13+
android {
14+
namespace = "com.infomaniak.core.inappupdate"
15+
compileSdk = coreCompileSdk
16+
17+
defaultConfig {
18+
minSdk = coreMinSdk
19+
20+
buildConfigField("String", "INFOMANIAK_API_V1", "\"https://api.infomaniak.com/1\"")
21+
22+
consumerProguardFiles("consumer-rules.pro")
23+
}
24+
25+
buildTypes {
26+
release {
27+
isMinifyEnabled = false
28+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
29+
}
30+
}
31+
compileOptions {
32+
sourceCompatibility = javaVersion
33+
targetCompatibility = javaVersion
34+
}
35+
kotlinOptions {
36+
jvmTarget = javaVersion.toString()
37+
}
38+
39+
flavorDimensions += "distribution"
40+
productFlavors {
41+
create("standard") {
42+
isDefault = true
43+
}
44+
create("fdroid")
45+
}
46+
47+
buildFeatures {
48+
compose = true
49+
buildConfig = true
50+
}
51+
}
52+
53+
dependencies {
54+
implementation(project(":Core"))
55+
implementation(project(":Core:Network"))
56+
implementation(project(":Core:Sentry"))
57+
58+
implementation(core.androidx.datastore.preferences)
59+
implementation(core.appcompat)
60+
implementation(core.androidx.work.runtime.ktx)
61+
implementation(core.kotlinx.serialization.json)
62+
63+
implementation(core.app.update)
64+
implementation(core.app.update.ktx)
65+
66+
implementation(core.androidx.concurrent.futures.ktx)
67+
68+
implementation(core.okhttp)
69+
implementation(core.gson)
70+
71+
// Compose
72+
implementation(core.coil.compose)
73+
implementation(core.coil.network.okhttp)
74+
implementation(platform(core.compose.bom))
75+
implementation(core.compose.runtime)
76+
debugImplementation(core.compose.ui.tooling)
77+
implementation(core.compose.material3)
78+
implementation(core.compose.ui)
79+
implementation(core.compose.ui.tooling.preview)
80+
}

InAppUpdate/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Infomaniak Core - Android
3+
* Copyright (C) 2024 Infomaniak Network SA
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.infomaniak.core.inappupdate
19+
20+
import android.content.Context
21+
import androidx.work.WorkManager
22+
23+
class AppUpdateScheduler(
24+
appContext: Context,
25+
workManager: WorkManager = WorkManager.getInstance(appContext),
26+
) : UpdateScheduler(appContext, workManager)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Infomaniak Core - Android
3+
* Copyright (C) 2023-2024 Infomaniak Network SA
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.infomaniak.core.inappupdate
19+
20+
import com.infomaniak.lib.stores.updaterequired.data.models.AppVersion.Store
21+
22+
object StoreUtils : StoresUtils {
23+
const val APP_UPDATE_TAG = "appUpdateFDroid"
24+
override val REQUIRED_UPDATE_STORE = Store.FDROID
25+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Infomaniak Core - Android
3+
* Copyright (C) 2024 Infomaniak Network SA
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.infomaniak.core.inappupdate.updatemanagers
19+
20+
import androidx.activity.ComponentActivity
21+
import androidx.lifecycle.lifecycleScope
22+
import com.infomaniak.lib.core.fdroidTools.FdroidApiTools
23+
import com.infomaniak.lib.core.utils.SentryLog
24+
import com.infomaniak.lib.stores.BaseInAppUpdateManager
25+
import com.infomaniak.lib.stores.StoreUtils
26+
import kotlinx.coroutines.Dispatchers
27+
import kotlinx.coroutines.launch
28+
import kotlinx.coroutines.withContext
29+
30+
class InAppUpdateManager(
31+
private val activity: ComponentActivity,
32+
) : BaseInAppUpdateManager(activity) {
33+
34+
override fun checkUpdateIsAvailable() {
35+
SentryLog.d(StoreUtils.APP_UPDATE_TAG, "Checking for update on FDroid")
36+
activity.lifecycleScope.launch(Dispatchers.IO) {
37+
val lastVersionCode = FdroidApiTools().getLastRelease(appId)
38+
39+
withContext(Dispatchers.Main) { onFDroidResult?.invoke(versionCode < lastVersionCode) }
40+
}
41+
}
42+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Infomaniak Core - Android
3+
* Copyright (C) 2024 Infomaniak Network SA
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.infomaniak.core.inappupdate.updatemanagers
19+
20+
import android.content.Context
21+
22+
internal class WorkerUpdateManager(appContext: Context) {
23+
24+
fun installDownloadedUpdate(onInstallFailure: (Exception) -> Unit, onInstallSuccess: () -> Unit) = Unit
25+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Infomaniak Core - Android
3+
* Copyright (C) 2024 Infomaniak Network SA
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.infomaniak.core.inappupdate
19+
20+
import android.content.Context
21+
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
22+
import androidx.datastore.preferences.core.Preferences
23+
import androidx.datastore.preferences.core.booleanPreferencesKey
24+
import androidx.datastore.preferences.core.edit
25+
import androidx.datastore.preferences.core.emptyPreferences
26+
import androidx.datastore.preferences.core.intPreferencesKey
27+
import androidx.datastore.preferences.preferencesDataStore
28+
import com.infomaniak.core.sentry.SentryLog
29+
import kotlinx.coroutines.flow.first
30+
import kotlinx.coroutines.flow.map
31+
32+
private val Context.dataStore by preferencesDataStore(
33+
name = AppUpdateSettingsRepository.DATA_STORE_NAME,
34+
// In case we have a CorruptionException, we want to clear all DataStore preferences
35+
corruptionHandler = ReplaceFileCorruptionHandler<Preferences> { emptyPreferences() },
36+
)
37+
38+
@Suppress("UNCHECKED_CAST")
39+
class AppUpdateSettingsRepository(private val context: Context) {
40+
41+
fun <T> flowOf(key: Preferences.Key<T>) = context.dataStore.data.map { it[key] ?: (getDefaultValue(key) as T) }
42+
43+
suspend fun <T> getValue(key: Preferences.Key<T>) = runCatching {
44+
flowOf(key).first()
45+
}.getOrElse { exception ->
46+
SentryLog.e(TAG, "Error while trying to get value from DataStore for key : $key", exception)
47+
getDefaultValue(key) as T
48+
}
49+
50+
private fun <T> getDefaultValue(key: Preferences.Key<T>) = when (key) {
51+
IS_USER_WANTING_UPDATES_KEY -> DEFAULT_IS_USER_WANTING_UPDATES
52+
HAS_APP_UPDATE_DOWNLOADED_KEY -> DEFAULT_HAS_APP_UPDATE_DOWNLOADED
53+
APP_UPDATE_LAUNCHES_KEY -> DEFAULT_APP_UPDATE_LAUNCHES
54+
else -> throw IllegalArgumentException("Unknown Preferences.Key")
55+
}
56+
57+
suspend fun <T> setValue(key: Preferences.Key<T>, value: T) {
58+
runCatching {
59+
context.dataStore.edit { it[key] = value }
60+
}.onFailure { exception ->
61+
SentryLog.e(TAG, "Error while trying to set value into DataStore for key : $key", exception)
62+
}
63+
}
64+
65+
suspend fun resetUpdateSettings() {
66+
// This avoid the user being instantly reprompted to download update
67+
setValue(IS_USER_WANTING_UPDATES_KEY, DEFAULT_IS_USER_WANTING_UPDATES)
68+
setValue(HAS_APP_UPDATE_DOWNLOADED_KEY, DEFAULT_HAS_APP_UPDATE_DOWNLOADED)
69+
setValue(APP_UPDATE_LAUNCHES_KEY, DEFAULT_APP_UPDATE_LAUNCHES)
70+
}
71+
72+
companion object {
73+
74+
private const val TAG = "AppUpdateSettingsRepository"
75+
76+
const val DEFAULT_APP_UPDATE_LAUNCHES = 20
77+
78+
val IS_USER_WANTING_UPDATES_KEY = booleanPreferencesKey("isUserWantingUpdatesKey")
79+
val HAS_APP_UPDATE_DOWNLOADED_KEY = booleanPreferencesKey("hasAppUpdateDownloadedKey")
80+
val APP_UPDATE_LAUNCHES_KEY = intPreferencesKey("appUpdateLaunchesKey")
81+
82+
internal const val DATA_STORE_NAME = "StoresSettingsDataStore"
83+
84+
private const val DEFAULT_IS_USER_WANTING_UPDATES = false
85+
private const val DEFAULT_HAS_APP_UPDATE_DOWNLOADED = false
86+
}
87+
}

0 commit comments

Comments
 (0)