Skip to content

Commit dbbdbdc

Browse files
phpbgsamuelchemla
andauthored
Add Trial (#21)
* Add trial flavor --------- Co-authored-by: Samuel CHEMLA <[email protected]>
1 parent fa75c9c commit dbbdbdc

File tree

12 files changed

+152
-8
lines changed

12 files changed

+152
-8
lines changed

app/build.gradle

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ android {
3232
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
3333
}
3434
}
35+
flavorDimensions += "version"
36+
productFlavors {
37+
freeVersion {
38+
dimension "version"
39+
applicationId "com.phpbg.easysync.trial"
40+
resValue "string", "flavored_app_name", "Easy Sync Trial"
41+
42+
}
43+
paidVersion {
44+
dimension "version"
45+
resValue "string", "flavored_app_name", "Easy Sync"
46+
}
47+
}
3548
compileOptions {
3649
sourceCompatibility JavaVersion.VERSION_1_8
3750
targetCompatibility JavaVersion.VERSION_1_8

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
android:dataExtractionRules="@xml/data_extraction_rules"
2121
android:fullBackupContent="@xml/backup_rules"
2222
android:icon="@mipmap/ic_launcher"
23-
android:label="@string/app_name"
23+
android:label="@string/flavored_app_name"
2424
android:localeConfig="@xml/locales_config"
2525
android:networkSecurityConfig="@xml/network_security_config"
2626
android:roundIcon="@mipmap/ic_launcher_round"

app/src/main/java/com/phpbg/easysync/MyApp.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,36 @@
2525
package com.phpbg.easysync
2626

2727
import android.app.Application
28+
import android.content.Context
2829
import android.os.StrictMode
30+
import java.lang.System.currentTimeMillis
31+
import kotlin.math.floor
32+
import kotlin.math.max
33+
34+
const val TRIAL_DURATION_DAYS = 30
2935

3036
class MyApp : Application() {
3137
init {
3238
if (BuildConfig.DEBUG)
3339
StrictMode.enableDefaults()
3440
}
41+
42+
companion object {
43+
fun isTrial(): Boolean {
44+
@Suppress("KotlinConstantConditions")
45+
return (BuildConfig.FLAVOR == "freeVersion")
46+
}
47+
48+
fun getTrialRemainingDays(context: Context): Int {
49+
val installed = context
50+
.packageManager
51+
.getPackageInfo(context.packageName, 0).firstInstallTime
52+
val now = currentTimeMillis()
53+
return max(0, TRIAL_DURATION_DAYS- floor((now-installed)/(24*3600*1000).toFloat()).toInt())
54+
}
55+
56+
fun isTrialExpired(context: Context): Boolean {
57+
return isTrial() && getTrialRemainingDays(context) == 0
58+
}
59+
}
3560
}

app/src/main/java/com/phpbg/easysync/Notifications.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@
2525
package com.phpbg.easysync
2626

2727
enum class Notifications(val id: Int) {
28-
MISSING_PERMISSIONS(1)
28+
MISSING_PERMISSIONS(1),
29+
TRIAL_EXPIRED(2)
2930
}

app/src/main/java/com/phpbg/easysync/ui/MainActivity.kt

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
package com.phpbg.easysync.ui
2626

27+
import android.content.ActivityNotFoundException
2728
import android.content.Intent
2829
import android.content.res.Configuration
2930
import android.net.Uri
@@ -47,6 +48,7 @@ import androidx.compose.material.icons.Icons
4748
import androidx.compose.material.icons.filled.Cancel
4849
import androidx.compose.material.icons.filled.CheckCircle
4950
import androidx.compose.material.icons.filled.Help
51+
import androidx.compose.material.icons.filled.Info
5052
import androidx.compose.material.icons.filled.Schedule
5153
import androidx.compose.material.icons.filled.Settings
5254
import androidx.compose.material.icons.filled.Warning
@@ -58,15 +60,18 @@ import androidx.compose.material3.SnackbarHostState
5860
import androidx.compose.material3.Surface
5961
import androidx.compose.material3.Text
6062
import androidx.compose.runtime.Composable
63+
import androidx.compose.runtime.IntState
6164
import androidx.compose.runtime.State
6265
import androidx.compose.runtime.livedata.observeAsState
66+
import androidx.compose.runtime.mutableIntStateOf
6367
import androidx.compose.runtime.mutableStateOf
6468
import androidx.compose.runtime.remember
6569
import androidx.compose.runtime.rememberCoroutineScope
6670
import androidx.compose.ui.Alignment
6771
import androidx.compose.ui.Modifier
6872
import androidx.compose.ui.graphics.Color
6973
import androidx.compose.ui.platform.LocalContext
74+
import androidx.compose.ui.res.pluralStringResource
7075
import androidx.compose.ui.res.stringResource
7176
import androidx.compose.ui.tooling.preview.Preview
7277
import androidx.compose.ui.unit.dp
@@ -126,7 +131,9 @@ class MainActivity : ComponentActivity() {
126131
showDavStatus = viewModel.showDavStatus,
127132
isDavLoading = viewModel.isDavLoading,
128133
isDavConnected = viewModel.isDavConnected,
129-
hasOptionalPermissions = hasOptionalPermissions
134+
isTrial = viewModel.isTrial,
135+
hasOptionalPermissions = hasOptionalPermissions,
136+
trialRemainingDays = viewModel.trialRemainingDays
130137
)
131138
}
132139
}
@@ -148,7 +155,9 @@ private fun Main(
148155
showDavStatus: State<Boolean>,
149156
isDavLoading: State<Boolean>,
150157
isDavConnected: State<Boolean>,
158+
isTrial: State<Boolean>,
151159
hasOptionalPermissions: State<Boolean>,
160+
trialRemainingDays: IntState,
152161
) {
153162
val mContext = LocalContext.current
154163
val syncEnabled = workerState == null || workerState != WorkInfo.State.RUNNING
@@ -163,7 +172,7 @@ private fun Main(
163172
.padding(16.dp)
164173
.verticalScroll(rememberScrollState())
165174
) {
166-
Title(text = stringResource(R.string.app_name))
175+
Title(text = stringResource(R.string.flavored_app_name))
167176

168177
val davSettingsHandler = fun(_: Int) {
169178
val myIntent = Intent(mContext, DavSettingsActivity::class.java)
@@ -247,6 +256,23 @@ private fun Main(
247256
},
248257
)
249258

259+
if (isTrial.value) {
260+
val msg = if (trialRemainingDays.intValue == 0) stringResource(R.string.home_trial_over) else pluralStringResource(R.plurals.home_trial_days_left, trialRemainingDays.intValue, trialRemainingDays.intValue)
261+
StatusTitleClickable(
262+
title = null,
263+
actionTitle = msg,
264+
statusColor = Color.Gray,
265+
statusIcon = Icons.Default.Info,
266+
clickHandler = {
267+
try {
268+
mContext.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.phpbg.easysync")))
269+
} catch (e: ActivityNotFoundException) {
270+
mContext.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.phpbg.easysync")))
271+
}
272+
},
273+
)
274+
}
275+
250276
Spacer(modifier = Modifier.height(16.dp))
251277

252278
val syncedPercent = if (localCount > 0 && syncedCount >= 0) {
@@ -354,7 +380,29 @@ private fun MainPreview() {
354380
showDavStatus = remember { mutableStateOf(true) },
355381
isDavLoading = remember { mutableStateOf(false) },
356382
isDavConnected = remember { mutableStateOf(true) },
383+
isTrial = remember { mutableStateOf(false) },
384+
hasOptionalPermissions = remember { mutableStateOf(false) },
385+
trialRemainingDays = remember { mutableIntStateOf(0) }
386+
)
387+
}
388+
}
389+
390+
@Preview(name = "Trial Mode", showBackground = false)
391+
@Composable
392+
private fun MainPreviewTrial() {
393+
MyApplicationTheme {
394+
Main(
395+
fullSyncNowHandler = {},
396+
workerState = WorkInfo.State.RUNNING,
397+
syncedCount = 10000,
398+
localCount = 100,
399+
jobCount = -1,
400+
showDavStatus = remember { mutableStateOf(true) },
401+
isDavLoading = remember { mutableStateOf(false) },
402+
isDavConnected = remember { mutableStateOf(true) },
403+
isTrial = remember { mutableStateOf(true) },
357404
hasOptionalPermissions = remember { mutableStateOf(false) },
405+
trialRemainingDays = remember { mutableIntStateOf(28) }
358406
)
359407
}
360408
}

app/src/main/java/com/phpbg/easysync/ui/MainViewModel.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ import android.os.Handler
3232
import android.os.Looper
3333
import android.provider.MediaStore
3434
import android.util.Log
35+
import androidx.compose.runtime.mutableIntStateOf
3536
import androidx.compose.runtime.mutableStateOf
3637
import androidx.lifecycle.AndroidViewModel
3738
import androidx.lifecycle.LiveData
3839
import androidx.lifecycle.MutableLiveData
3940
import androidx.lifecycle.map
4041
import androidx.lifecycle.viewModelScope
4142
import androidx.work.WorkInfo
43+
import com.phpbg.easysync.MyApp
4244
import com.phpbg.easysync.dav.CollectionPath
4345
import com.phpbg.easysync.dav.MisconfigurationException
4446
import com.phpbg.easysync.dav.WebDavService
@@ -85,6 +87,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
8587
val isDavConnected = mutableStateOf(false)
8688
val isDavLoading = mutableStateOf(false)
8789

90+
val isTrial = mutableStateOf(false)
91+
val trialRemainingDays = mutableIntStateOf(0)
92+
8893
val workInfosList = FullSyncWorker.getLiveData(this.getApplication()).map { x ->
8994
if (x.isEmpty()) {
9095
return@map null
@@ -120,6 +125,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
120125
viewModelScope.launch {
121126
loadDav()
122127
}
128+
129+
isTrial.value = MyApp.isTrial()
130+
trialRemainingDays.intValue = MyApp.getTrialRemainingDays(getApplication())
123131
}
124132

125133
private fun loadImages() {

app/src/main/java/com/phpbg/easysync/worker/FileDetectWorker.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import androidx.work.ExistingWorkPolicy
3232
import androidx.work.OneTimeWorkRequestBuilder
3333
import androidx.work.WorkManager
3434
import androidx.work.WorkerParameters
35+
import com.phpbg.easysync.MyApp
3536
import com.phpbg.easysync.mediastore.MediaStoreService
3637
import com.phpbg.easysync.mediastore.URIS
3738

@@ -45,6 +46,9 @@ private const val TAG = "FileDetectWorker"
4546
class FileDetectWorker(appContext: Context, workerParams: WorkerParameters) :
4647
CoroutineWorker(appContext, workerParams) {
4748
override suspend fun doWork(): Result {
49+
if (MyApp.isTrialExpired(this.applicationContext)) {
50+
return Result.success()
51+
}
4852
try {
4953
_doWork()
5054
} catch (e: Exception) {

app/src/main/java/com/phpbg/easysync/worker/FullSyncWorker.kt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import androidx.work.PeriodicWorkRequestBuilder
3939
import androidx.work.WorkInfo
4040
import androidx.work.WorkManager
4141
import androidx.work.WorkerParameters
42+
import com.phpbg.easysync.MyApp.Companion.isTrialExpired
4243
import com.phpbg.easysync.Notifications
4344
import com.phpbg.easysync.R
4445
import com.phpbg.easysync.dav.MisconfigurationException
@@ -57,6 +58,10 @@ class FullSyncWorker(context: Context, parameters: WorkerParameters) :
5758
NotificationManager
5859

5960
override suspend fun doWork(): Result {
61+
if (isTrialExpired(this.applicationContext)) {
62+
showTrialExpiredNotification()
63+
return Result.success()
64+
}
6065
val immediate = inputData.getBoolean(IMMEDIATE_KEY, false)
6166
return try {
6267
val syncService = SyncService.getInstance(this.applicationContext)
@@ -83,17 +88,28 @@ class FullSyncWorker(context: Context, parameters: WorkerParameters) :
8388
}
8489
}
8590

91+
private fun showTrialExpiredNotification() {
92+
val title = applicationContext.getString(R.string.notification_trial_over_title)
93+
val text = applicationContext.getString(R.string.notification_trial_over_text)
94+
val notificationId = Notifications.TRIAL_EXPIRED
95+
showNotification(title, text, notificationId)
96+
}
97+
8698
private fun showMissingPermissionsNotification() {
87-
val id = applicationContext.getString(R.string.notification_channel_id)
8899
val title = applicationContext.getString(R.string.notification_missing_permissions_title)
89100
val text = applicationContext.getString(R.string.notification_missing_permissions_text)
101+
val notificationId = Notifications.MISSING_PERMISSIONS
102+
showNotification(title, text, notificationId)
103+
}
104+
105+
private fun showNotification(title: String, text: String, notificationId: Notifications) {
106+
val id = applicationContext.getString(R.string.notification_channel_id)
90107

91108
val intent = Intent(this.applicationContext, MainActivity::class.java).apply {
92109
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
93110
}
94111
val pendingIntent = PendingIntent.getActivity(this.applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE)
95112

96-
97113
createChannel(id)
98114
val notification = Notification.Builder(applicationContext, id)
99115
.setContentTitle(title)
@@ -105,7 +121,7 @@ class FullSyncWorker(context: Context, parameters: WorkerParameters) :
105121
.setContentIntent(pendingIntent)
106122
.build()
107123

108-
notificationManager.notify(Notifications.MISSING_PERMISSIONS.id, notification)
124+
notificationManager.notify(notificationId.id, notification)
109125
}
110126

111127
private fun createChannel(channelId: String) {

app/src/main/res/values-de/strings.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<resources>
33
<string name="notification_missing_permissions_text">Bitte erteilen Sie Berechtigungen, um die Synchronisation zu ermöglichen</string>
44
<string name="notification_missing_permissions_title">Synchronisation fehlgeschlagen</string>
5+
<string name="notification_trial_over_title">Synchronisation gestoppt</string>
6+
<string name="notification_trial_over_text">Bitte kaufen Sie die Vollversion</string>
57
<string name="dav_settings_activity_title">Einstellungen</string>
68
<string name="dav_settings_hide_password">Passwort ausblenden</string>
79
<string name="dav_settings_password">Passwort</string>
@@ -25,6 +27,11 @@
2527
<string name="home_permissions_action_fix">Beheben…</string>
2628
<string name="home_sync_running">läuft</string>
2729
<string name="home_sync_syncing">synchronisiert</string>
30+
<string name="home_trial_over">Die Testversion ist abgelaufen, bitte kaufen Sie die Vollversion</string>
31+
<plurals name="home_trial_days_left">
32+
<item quantity="one">Testversion: %d Tag verbleibend</item>
33+
<item quantity="other">Testversion: %d Tage verbleibend</item>
34+
</plurals>
2835
<string name="permissions_activity_title">Berechtigungen</string>
2936
<string name="permissions_files_text">Um Ihre Dateien zu synchronisieren, benötigen wir vollen Zugriff auf Ihre Dateien. Gehen Sie zu den Einstellungen, um die Berechtigung zu aktivieren</string>
3037
<string name="permissions_files_button">Dateieinstellungen</string>

app/src/main/res/values-fr/strings.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<resources>
33
<string name="notification_missing_permissions_text">Veuillez accorder les permissions pour permettre la synchronisation</string>
44
<string name="notification_missing_permissions_title">Synchronisation KO</string>
5+
<string name="notification_trial_over_title">Synchronisation interrompue</string>
6+
<string name="notification_trial_over_text">Veuillez acheter la version complète</string>
57
<string name="dav_settings_activity_title">Paramètres</string>
68
<string name="dav_settings_username">Nom d\'utilisateur</string>
79
<string name="dav_settings_password">Mot de passe</string>
@@ -23,6 +25,11 @@
2325
<string name="home_permissions_action_fix">Corriger…</string>
2426
<string name="home_sync_running">attente</string>
2527
<string name="home_sync_syncing">en cours</string>
28+
<string name="home_trial_over">La période d\'essai est terminée, veuillez acheter la version complète</string>
29+
<plurals name="home_trial_days_left">
30+
<item quantity="one">Essai gratuit : %d jour restant</item>
31+
<item quantity="other">Essai gratuit : %d jours restants</item>
32+
</plurals>
2633
<string name="dav_settings_title">Paramètres DAV</string>
2734
<string name="dav_settings_url_insecure">Cette URL n\'est pas sécurisée. Vos données pourront être interceptées. Vous devriez utiliser HTTPS.</string>
2835
<string name="permissions_activity_title">Permissions</string>

0 commit comments

Comments
 (0)