Skip to content

Commit a39e3e7

Browse files
author
Mihai-Cristian Condrea
committed
feat: Enhanced in-app updates and improved Ukrainian translations
This update brings several improvements to the application, including a refined in-app update process and updates to the Ukrainian translations. - Implemented a new in-app update process that now supports immediate and flexible updates based on the staleness of the client version. - The app will prompt for an immediate update if the client version is older than 90 days. - Introduced a new `updateResultLauncher` to handle the update result more efficiently. - Updated Ukrainian string resources to streamline content. - Updated AppToolkit library to version 0.0.49. - Updated the app build version to 114, and version name to 1.2.0. - Added a string resource for the feedback email title. - Removed unused code related to update notifications and email sending. - Removed unused strings resources.
1 parent faefd8c commit a39e3e7

File tree

11 files changed

+66
-202
lines changed

11 files changed

+66
-202
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ android {
2020
applicationId = "com.d4rk.androidtutorials"
2121
minSdk = 23
2222
targetSdk = 35
23-
versionCode = 112
23+
versionCode = 114
2424
versionName = "1.2.0"
2525
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2626
@Suppress("UnstableApiUsage")
@@ -109,7 +109,7 @@ android {
109109
dependencies {
110110

111111
// App Core
112-
implementation(dependencyNotation = "com.github.D4rK7355608:AppToolkit:0.0.48") {
112+
implementation(dependencyNotation = "com.github.D4rK7355608:AppToolkit:0.0.49") {
113113
isTransitive = true
114114
}
115115

app/src/main/kotlin/com/d4rk/androidtutorials/ui/screens/help/HelpScreen.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ import com.d4rk.android.libs.apptoolkit.ui.components.modifiers.bounceClick
5959
import com.d4rk.android.libs.apptoolkit.ui.components.spacers.LargeHorizontalSpacer
6060
import com.d4rk.android.libs.apptoolkit.ui.components.spacers.MediumVerticalSpacer
6161
import com.d4rk.android.libs.apptoolkit.ui.components.spacers.SmallVerticalSpacer
62+
import com.d4rk.android.libs.apptoolkit.utils.helpers.IntentsHelper
6263
import com.d4rk.android.libs.apptoolkit.utils.rememberHtmlData
6364
import com.d4rk.androidtutorials.BuildConfig
65+
import com.d4rk.androidtutorials.R
6466
import com.d4rk.androidtutorials.data.model.ui.screens.UiHelpQuestion
6567
import com.d4rk.androidtutorials.data.model.ui.screens.UiHelpScreen
6668
import com.d4rk.androidtutorials.ui.components.navigation.TopAppBarScaffoldWithBackButtonAndActions
@@ -131,7 +133,7 @@ fun HelpScreen(activity : Activity , viewModel : HelpViewModel) {
131133
MediumVerticalSpacer()
132134
ContactUsCard(onClick = {
133135
view.playSoundEffect(SoundEffectConstants.CLICK)
134-
viewModel.sendEmailToDeveloper(activity = activity)
136+
IntentsHelper.sendEmailToDeveloper(context = activity , applicationNameRes = R.string.app_name)
135137
})
136138
Spacer(modifier = Modifier.height(height = 64.dp))
137139
}
@@ -223,4 +225,4 @@ fun ContactUsCard(onClick : () -> Unit) {
223225
}
224226
}
225227
}
226-
}
228+
}

app/src/main/kotlin/com/d4rk/androidtutorials/ui/screens/help/HelpViewModel.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,4 @@ class HelpViewModel(application : Application) : BaseViewModel(application) {
5353
repository.launchReviewFlowRepository(activity = activity , reviewInfo = reviewInfo)
5454
}
5555
}
56-
57-
fun sendEmailToDeveloper(activity: Activity) {
58-
viewModelScope.launch(context = coroutineExceptionHandler) {
59-
repository.sendEmailToDeveloperRepository(activity = activity)
60-
}
61-
}
6256
}

app/src/main/kotlin/com/d4rk/androidtutorials/ui/screens/help/repository/HelpRepository.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ class HelpRepository(application : Application) : HelpRepositoryImplementation(a
1313
withContext(Dispatchers.IO) {
1414
val questions = getFAQsImplementation().map { (questionRes , summaryRes) ->
1515
UiHelpQuestion(
16-
question = application.getString(questionRes) ,
17-
answer = application.getString(summaryRes)
16+
question = application.getString(questionRes) , answer = application.getString(summaryRes)
1817
)
1918
}.toCollection(destination = ArrayList())
2019

@@ -37,10 +36,4 @@ class HelpRepository(application : Application) : HelpRepositoryImplementation(a
3736
launchReviewFlowImplementation(activity = activity , reviewInfo = reviewInfo)
3837
}
3938
}
40-
41-
suspend fun sendEmailToDeveloperRepository(activity: Activity) {
42-
withContext(Dispatchers.IO) {
43-
sendEmailToDeveloperImplementation(activity = activity)
44-
}
45-
}
46-
}
39+
}

app/src/main/kotlin/com/d4rk/androidtutorials/ui/screens/help/repository/HelpRepositoryImplementation.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ abstract class HelpRepositoryImplementation(
4242
}
4343
}.addOnFailureListener {
4444
IntentsHelper.openUrl(
45-
context = application ,
46-
url = "https://play.google.com/store/apps/details?id=$packageName&showAllReviews=true"
45+
context = application , url = "https://play.google.com/store/apps/details?id=$packageName&showAllReviews=true"
4746
)
4847
}
4948
}
@@ -52,8 +51,4 @@ abstract class HelpRepositoryImplementation(
5251
val reviewManager : ReviewManager = ReviewManagerFactory.create(activity)
5352
reviewManager.launchReviewFlow(activity , reviewInfo)
5453
}
55-
56-
fun sendEmailToDeveloperImplementation(activity : Activity) {
57-
IntentsHelper.sendEmailToDeveloper(context = activity, applicationName = R.string.app_name)
58-
}
5954
}

app/src/main/kotlin/com/d4rk/androidtutorials/ui/screens/main/MainActivity.kt

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import android.os.Build
55
import android.os.Bundle
66
import androidx.activity.compose.setContent
77
import androidx.activity.enableEdgeToEdge
8+
import androidx.activity.result.ActivityResultLauncher
9+
import androidx.activity.result.IntentSenderRequest
10+
import androidx.activity.result.contract.ActivityResultContracts
811
import androidx.activity.viewModels
912
import androidx.annotation.RequiresApi
1013
import androidx.appcompat.app.AppCompatActivity
@@ -14,7 +17,6 @@ import androidx.compose.material3.Surface
1417
import androidx.compose.ui.Modifier
1518
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
1619
import com.d4rk.android.libs.apptoolkit.notifications.managers.AppUpdateNotificationsManager
17-
import com.d4rk.androidtutorials.R
1820
import com.d4rk.androidtutorials.data.core.AppCoreManager
1921
import com.d4rk.androidtutorials.ui.screens.settings.display.theme.style.AppTheme
2022
import com.google.android.gms.ads.MobileAds
@@ -28,12 +30,13 @@ class MainActivity : AppCompatActivity() {
2830
private val viewModel : MainViewModel by viewModels()
2931
private lateinit var appUpdateManager : AppUpdateManager
3032
private lateinit var appUpdateNotificationsManager : AppUpdateNotificationsManager
33+
private lateinit var updateResultLauncher : ActivityResultLauncher<IntentSenderRequest>
3134

3235
override fun onCreate(savedInstanceState : Bundle?) {
3336
super.onCreate(savedInstanceState)
3437
installSplashScreen().apply {
3538
setKeepOnScreenCondition {
36-
!(application as AppCoreManager).isAppLoaded()
39+
! (application as AppCoreManager).isAppLoaded()
3740
}
3841
}
3942
enableEdgeToEdge()
@@ -55,7 +58,9 @@ class MainActivity : AppCompatActivity() {
5558
with(receiver = viewModel) {
5659
checkAndHandleStartup()
5760
configureSettings()
58-
checkForUpdates(activity = this@MainActivity , appUpdateManager = appUpdateManager)
61+
viewModel.checkForUpdates(
62+
appUpdateManager = appUpdateManager , updateResultLauncher = updateResultLauncher
63+
)
5964
checkAndScheduleUpdateNotifications(appUpdateNotificationsManager = appUpdateNotificationsManager)
6065
checkAppUsageNotifications(context = this@MainActivity)
6166
}
@@ -74,11 +79,10 @@ class MainActivity : AppCompatActivity() {
7479
@Deprecated("Deprecated in Java")
7580
@Suppress("DEPRECATION")
7681
override fun onBackPressed() {
77-
MaterialAlertDialogBuilder(this).setTitle(com.d4rk.android.libs.apptoolkit.R.string.close).setMessage(com.d4rk.android.libs.apptoolkit.R.string.summary_close)
78-
.setPositiveButton(android.R.string.yes) { _ , _ ->
79-
super.onBackPressed()
80-
moveTaskToBack(true)
81-
}.setNegativeButton(android.R.string.no , null).apply { show() }
82+
MaterialAlertDialogBuilder(this).setTitle(com.d4rk.android.libs.apptoolkit.R.string.close).setMessage(com.d4rk.android.libs.apptoolkit.R.string.summary_close).setPositiveButton(android.R.string.yes) { _ , _ ->
83+
super.onBackPressed()
84+
moveTaskToBack(true)
85+
}.setNegativeButton(android.R.string.no , null).apply { show() }
8286
}
8387

8488
/**
@@ -112,8 +116,14 @@ class MainActivity : AppCompatActivity() {
112116

113117
private fun initializeActivityComponents() {
114118
MobileAds.initialize(this@MainActivity)
119+
updateResultLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
120+
when (result.resultCode) {
121+
RESULT_OK -> showUpdateSuccessfulSnackbar()
122+
else -> showUpdateFailedSnackbar()
123+
}
124+
}
115125
appUpdateManager = AppUpdateManagerFactory.create(this@MainActivity)
116-
appUpdateNotificationsManager = AppUpdateNotificationsManager(context = this, channelId = "update_channel")
126+
appUpdateNotificationsManager = AppUpdateNotificationsManager(context = this , channelId = "update_channel")
117127
}
118128

119129
private fun showUpdateSuccessfulSnackbar() {
@@ -135,7 +145,7 @@ class MainActivity : AppCompatActivity() {
135145
findViewById(android.R.id.content) , com.d4rk.android.libs.apptoolkit.R.string.snack_update_failed , Snackbar.LENGTH_LONG
136146
).setAction(com.d4rk.android.libs.apptoolkit.R.string.try_again) {
137147
viewModel.checkForUpdates(
138-
activity = this@MainActivity , appUpdateManager = appUpdateManager
148+
appUpdateManager = appUpdateManager , updateResultLauncher = updateResultLauncher
139149
)
140150
}
141151
snackbar.show()

app/src/main/kotlin/com/d4rk/androidtutorials/ui/screens/main/MainViewModel.kt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package com.d4rk.androidtutorials.ui.screens.main
22

3-
import android.app.Activity
43
import android.app.Application
54
import android.content.Context
65
import android.os.Build
6+
import androidx.activity.result.ActivityResultLauncher
7+
import androidx.activity.result.IntentSenderRequest
78
import androidx.annotation.RequiresApi
89
import androidx.compose.material.icons.Icons
910
import androidx.compose.material.icons.automirrored.outlined.EventNote
@@ -14,7 +15,6 @@ import androidx.lifecycle.viewModelScope
1415
import com.d4rk.android.libs.apptoolkit.data.model.ui.navigation.NavigationDrawerItem
1516
import com.d4rk.android.libs.apptoolkit.notifications.managers.AppUpdateNotificationsManager
1617
import com.d4rk.android.libs.apptoolkit.utils.helpers.IntentsHelper
17-
import com.d4rk.androidtutorials.R
1818
import com.d4rk.androidtutorials.data.core.AppCoreManager
1919
import com.d4rk.androidtutorials.data.model.ui.navigation.BottomNavigationScreen
2020
import com.d4rk.androidtutorials.data.model.ui.screens.UiMainScreen
@@ -28,16 +28,15 @@ import kotlinx.coroutines.launch
2828

2929
class MainViewModel(application : Application) : BaseViewModel(application) {
3030
private val repository = MainRepository(
31-
dataStore = AppCoreManager.dataStore ,
32-
application = application
31+
dataStore = AppCoreManager.dataStore , application = application
3332
)
3433
private val _uiState : MutableStateFlow<UiMainScreen> = MutableStateFlow(initializeUiState())
3534
val uiState : StateFlow<UiMainScreen> = _uiState
3635

37-
fun checkForUpdates(activity : Activity , appUpdateManager : AppUpdateManager) {
36+
fun checkForUpdates(updateResultLauncher : ActivityResultLauncher<IntentSenderRequest> , appUpdateManager : AppUpdateManager) {
3837
viewModelScope.launch(context = coroutineExceptionHandler) {
3938
repository.checkForUpdates(
40-
appUpdateManager = appUpdateManager , activity = activity
39+
appUpdateManager = appUpdateManager , updateResultLauncher = updateResultLauncher
4140
)
4241
}
4342
}
@@ -59,9 +58,7 @@ class MainViewModel(application : Application) : BaseViewModel(application) {
5958
selectedIcon = Icons.Outlined.Share ,
6059
)
6160
) , bottomNavigationItems = listOf(
62-
BottomNavigationScreen.Home ,
63-
BottomNavigationScreen.StudioBot ,
64-
BottomNavigationScreen.Favorites
61+
BottomNavigationScreen.Home , BottomNavigationScreen.StudioBot , BottomNavigationScreen.Favorites
6562
) , currentBottomNavigationScreen = BottomNavigationScreen.Home
6663
)
6764
}

app/src/main/kotlin/com/d4rk/androidtutorials/ui/screens/main/repository/MainRepository.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package com.d4rk.androidtutorials.ui.screens.main.repository
22

3-
import android.app.Activity
43
import android.app.Application
54
import android.content.Context
65
import android.os.Build
6+
import androidx.activity.result.ActivityResultLauncher
7+
import androidx.activity.result.IntentSenderRequest
78
import androidx.annotation.RequiresApi
89
import com.d4rk.android.libs.apptoolkit.notifications.managers.AppUpdateNotificationsManager
910
import com.d4rk.androidtutorials.data.datastore.DataStore
@@ -18,15 +19,14 @@ import kotlinx.coroutines.withContext
1819
* @property dataStore The data store used to persist settings and startup information.
1920
* @property application The application context.
2021
*/
21-
class MainRepository(dataStore : DataStore , application : Application) :
22-
MainRepositoryImplementation(application = application , dataStore = dataStore) {
22+
class MainRepository(dataStore : DataStore , application : Application) : MainRepositoryImplementation(application = application , dataStore = dataStore) {
2323

2424
suspend fun checkForUpdates(
25-
activity : Activity ,
25+
updateResultLauncher : ActivityResultLauncher<IntentSenderRequest> ,
2626
appUpdateManager : AppUpdateManager ,
2727
) {
2828
withContext(Dispatchers.IO) {
29-
checkForUpdatesImplementation(activity = activity , appUpdateManager = appUpdateManager)
29+
checkForUpdatesImplementation(updateResultLauncher = updateResultLauncher , appUpdateManager = appUpdateManager)
3030
}
3131
}
3232

app/src/main/kotlin/com/d4rk/androidtutorials/ui/screens/main/repository/MainRepositoryImplementation.kt

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import android.app.Activity
44
import android.app.Application
55
import android.content.Context
66
import android.os.Build
7+
import androidx.activity.result.ActivityResultLauncher
8+
import androidx.activity.result.IntentSenderRequest
79
import androidx.annotation.RequiresApi
810
import com.d4rk.android.libs.apptoolkit.notifications.managers.AppUpdateNotificationsManager
911
import com.d4rk.android.libs.apptoolkit.notifications.managers.AppUsageNotificationsManager
1012
import com.d4rk.androidtutorials.R
1113
import com.d4rk.androidtutorials.data.datastore.DataStore
1214
import com.google.android.play.core.appupdate.AppUpdateInfo
1315
import com.google.android.play.core.appupdate.AppUpdateManager
16+
import com.google.android.play.core.appupdate.AppUpdateOptions
1417
import com.google.android.play.core.install.model.ActivityResult
1518
import com.google.android.play.core.install.model.AppUpdateType
1619
import com.google.android.play.core.install.model.UpdateAvailability
@@ -26,7 +29,7 @@ import kotlinx.coroutines.tasks.await
2629
*
2730
* @property application The application context.
2831
*/
29-
abstract class MainRepositoryImplementation(val application : Application, val dataStore : DataStore) {
32+
abstract class MainRepositoryImplementation(val application : Application , val dataStore : DataStore) {
3033

3134
/**
3235
* Checks if the application is being launched for the first time.
@@ -57,33 +60,34 @@ abstract class MainRepositoryImplementation(val application : Application, val d
5760
}
5861

5962
suspend fun checkForUpdatesImplementation(
60-
activity: Activity,
61-
appUpdateManager: AppUpdateManager
62-
): Int {
63+
appUpdateManager : AppUpdateManager , updateResultLauncher : ActivityResultLauncher<IntentSenderRequest>
64+
) : Int {
6365
return runCatching {
64-
var updateResult: Int = Activity.RESULT_CANCELED
65-
val appUpdateInfo: AppUpdateInfo = appUpdateManager.appUpdateInfo.await()
66-
67-
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE &&
68-
appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) &&
69-
appUpdateInfo.updateAvailability() != UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
70-
) {
71-
appUpdateInfo.clientVersionStalenessDays()?.let { stalenessDays ->
72-
val updateType =
73-
if (stalenessDays > 90) AppUpdateType.IMMEDIATE else AppUpdateType.FLEXIBLE
74-
@Suppress("DEPRECATION")
75-
appUpdateManager.startUpdateFlowForResult(
76-
appUpdateInfo, updateType, activity, 1
77-
)
78-
updateResult = Activity.RESULT_OK
66+
val appUpdateInfo : AppUpdateInfo = appUpdateManager.appUpdateInfo.await()
67+
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
68+
val stalenessDays = appUpdateInfo.clientVersionStalenessDays() ?: 0
69+
val updateType = if (stalenessDays > 90) {
70+
AppUpdateType.IMMEDIATE
71+
}
72+
else {
73+
AppUpdateType.FLEXIBLE
7974
}
75+
76+
val appUpdateOptions : AppUpdateOptions = AppUpdateOptions.newBuilder(updateType).build()
77+
78+
val didStart : Boolean = appUpdateManager.startUpdateFlowForResult(
79+
appUpdateInfo , updateResultLauncher , appUpdateOptions
80+
)
81+
82+
if (didStart) return@runCatching Activity.RESULT_OK
8083
}
81-
updateResult
84+
Activity.RESULT_CANCELED
8285
}.getOrElse {
8386
ActivityResult.RESULT_IN_APP_UPDATE_FAILED
8487
}
8588
}
8689

90+
8791
@RequiresApi(Build.VERSION_CODES.O)
8892
fun checkAndScheduleUpdateNotificationsImplementation(appUpdateNotificationsManager : AppUpdateNotificationsManager) {
8993
appUpdateNotificationsManager.checkAndSendUpdateNotification()

0 commit comments

Comments
 (0)