Skip to content

Commit

Permalink
Feature: Soft app updates
Browse files Browse the repository at this point in the history
  • Loading branch information
vipulyaara committed Oct 5, 2024
1 parent 99046a7 commit c33938b
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 22 deletions.
37 changes: 30 additions & 7 deletions app/src/main/java/com/kafka/user/home/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,25 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.kafka.base.debug
import com.kafka.common.snackbar.SnackbarManager
import com.kafka.common.snackbar.asString
import com.kafka.common.widgets.LocalSnackbarHostState
import com.kafka.data.prefs.Theme
import com.kafka.navigation.Navigator
import com.kafka.navigation.NavigatorHost
import com.kafka.ui.components.snackbar.SnackbarMessagesHost
import com.kafka.user.R
import com.kafka.user.home.bottombar.HomeNavigation
import com.sarahang.playback.core.PlaybackConnection
import com.sarahang.playback.ui.audio.AudioActionHost
Expand All @@ -27,12 +37,6 @@ import com.sarahang.playback.ui.color.LocalColorExtractor
import kotlinx.coroutines.flow.collectLatest
import me.tatarka.inject.annotations.Assisted
import me.tatarka.inject.annotations.Inject
import com.kafka.base.debug
import com.kafka.common.snackbar.SnackbarManager
import com.kafka.common.widgets.LocalSnackbarHostState
import com.kafka.navigation.Navigator
import com.kafka.navigation.NavigatorHost
import com.kafka.ui.components.snackbar.SnackbarMessagesHost
import tm.alashow.datmusic.downloader.Downloader
import tm.alashow.datmusic.ui.downloader.DownloaderHost
import ui.common.theme.theme.LocalTheme
Expand All @@ -55,11 +59,30 @@ fun MainScreen(
) {
val mainViewModel = viewModel { viewModelFactory() }
val context = LocalContext.current
val appUpdateConfig by mainViewModel.appUpdateConfig.collectAsStateWithLifecycle()

ForceUpdateDialog(
show = mainViewModel.isUpdateRequired,
show = appUpdateConfig == MainViewModel.AppUpdateState.Required,
update = { mainViewModel.updateApp(context) })

LaunchedEffect(appUpdateConfig) {
if (appUpdateConfig == MainViewModel.AppUpdateState.Optional) {
snackbarManager.addMessage(
message = context.getString(R.string.app_update_is_available),
label = context.getString(R.string.update),
onClick = { mainViewModel.updateApp(context) }
)
}
}

LaunchedEffect(snackbarManager.actionPerformed) {
snackbarManager.actionPerformed.collectLatest { message ->
if (message.message.asString() == context.getString(R.string.app_update_is_available)) {
mainViewModel.updateApp(context)
}
}
}

LaunchedEffect(mainViewModel, navController) {
navController.currentBackStackEntryFlow.collectLatest { entry ->
mainViewModel.logScreenView(entry)
Expand Down
33 changes: 25 additions & 8 deletions app/src/main/java/com/kafka/user/home/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,41 @@ import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavBackStackEntry
import com.kafka.analytics.logger.Analytics
import com.kafka.base.extensions.stateInDefault
import com.kafka.common.goToPlayStore
import com.kafka.domain.interactors.account.SignInAnonymously
import com.kafka.domain.observers.ObserveAppUpdateConfig
import com.kafka.remote.config.RemoteConfig
import com.kafka.remote.config.getPlayerTheme
import com.kafka.remote.config.minSupportedVersion
import com.kafka.user.BuildConfig
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import com.kafka.analytics.logger.Analytics
import com.kafka.common.goToPlayStore
import com.kafka.domain.interactors.account.SignInAnonymously
import javax.inject.Inject

class MainViewModel @Inject constructor(
private val analytics: Analytics,
private val signInAnonymously: SignInAnonymously,
private val remoteConfig: RemoteConfig,
observeAppUpdateConfig: ObserveAppUpdateConfig
) : ViewModel() {
val playerTheme by lazy { remoteConfig.getPlayerTheme() }
val isUpdateRequired by lazy {
val minSupportedVersion = remoteConfig.minSupportedVersion()
minSupportedVersion != 0L && BuildConfig.VERSION_CODE < minSupportedVersion
}

private val versionCode = BuildConfig.VERSION_CODE
val appUpdateConfig = observeAppUpdateConfig.flow
.map {
when {
it.blockedAppVersions.contains(versionCode) -> AppUpdateState.Required
it.forceUpdateVersion > 0 && versionCode < it.forceUpdateVersion -> AppUpdateState.Required
it.softUpdateVersion > 0 && versionCode < it.softUpdateVersion -> AppUpdateState.Optional
else -> AppUpdateState.None
}
}.stateInDefault(viewModelScope, AppUpdateState.None)

init {
signInAnonymously()
observeAppUpdateConfig(Unit)
}

private fun signInAnonymously() {
Expand All @@ -47,4 +58,10 @@ class MainViewModel @Inject constructor(
arguments = entry.arguments,
)
}

enum class AppUpdateState {
Required,
Optional,
None
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
<string name="update_available" translatable="false">Update Available</string>
<string name="force_update_message" translatable="false">This version is outdated. Please update the app to continue using it.</string>
<string name="update" translatable="false">Update</string>
<string name="app_update_is_available">App update is available</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const val DOWNLOADER_TYPE = "downloader_type"
const val GOOGLE_LOGIN_ENABLED = "google_login_enabled"
const val RECOMMENDATION_ROW_ENABLED = "recommendation_row_enabled"
const val ONLINE_READER_ENABLED = "online_reader_enabled"
const val MIN_SUPPORTED_VERSION = "min_supported_version"
const val SHARE_APP_INDEX = "share_app_index"
const val DOWNLOADS_WARNING_MESSAGE = "downloads_warning_message"
const val ITEM_DETAIL_DYNAMIC_THEME_ENABLED = "item_detail_dynamic_theme_enabled"
Expand All @@ -28,8 +27,6 @@ fun RemoteConfig.isRecommendationRowEnabled() = getBoolean(RECOMMENDATION_ROW_EN

fun RemoteConfig.isOnlineReaderEnabled() = getBoolean(ONLINE_READER_ENABLED)

fun RemoteConfig.minSupportedVersion() = getLong(MIN_SUPPORTED_VERSION)

fun RemoteConfig.shareAppIndex() = getLong(SHARE_APP_INDEX)

fun RemoteConfig.downloadsWarningMessage() = get(DOWNLOADS_WARNING_MESSAGE)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.kafka.data.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class AppConfig(
@SerialName("app_update") val appUpdate: AppUpdateConfig,
)

@Serializable
data class AppUpdateConfig(
@SerialName("soft_update_version") val softUpdateVersion: Int,
@SerialName("force_update_version") val forceUpdateVersion: Int,
@SerialName("blocked_update_version") val blockedAppVersions: List<Int>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package com.kafka.data.feature.firestore

import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import dev.gitlive.firebase.firestore.CollectionReference
import com.kafka.base.ApplicationScope
import dev.gitlive.firebase.firestore.CollectionReference
import dev.gitlive.firebase.firestore.DocumentReference
import javax.inject.Inject
import dev.gitlive.firebase.firestore.FirebaseFirestore as FirebaseFirestoreKt

Expand All @@ -25,6 +26,11 @@ class FirestoreGraph @Inject constructor(
get() = firestoreKt
.collection("homepage-collection")

val appUpdateConfig: DocumentReference
get() = firestoreKt
.collection("app_config")
.document("app_update")

val feedbackCollection: CollectionReference
get() = firestoreKt.collection("feedback")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.kafka.domain.observers

import com.kafka.base.domain.SubjectInteractor
import com.kafka.data.feature.firestore.FirestoreGraph
import com.kafka.data.model.AppUpdateConfig
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class ObserveAppUpdateConfig @Inject constructor(
private val firestoreGraph: FirestoreGraph
) : SubjectInteractor<Unit, AppUpdateConfig>() {
override fun createObservable(params: Unit): Flow<AppUpdateConfig> {
return firestoreGraph.appUpdateConfig.snapshots
.map { it.data() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class SnackbarManager @Inject constructor() {
private val actionPerformedMessageChannel = Channel<SnackbarMessage<*>>(Channel.CONFLATED)

val messages = messagesChannel.receiveAsFlow()
val actionPerformed = actionPerformedMessageChannel.receiveAsFlow()
private val shownMessages = mutableSetOf<UiMessage>()

suspend fun addError(
Expand All @@ -38,6 +39,18 @@ class SnackbarManager @Inject constructor() {
observeMessageAction(message, onRetry)
}

suspend fun addMessage(
message: String,
label: String,
onClick: () -> Unit,
) {
val action = SnackbarAction(UiMessage.Plain(label), onClick)
val snackbarMessage = SnackbarMessage(UiMessage.Plain(message), action)
addMessage(SnackbarMessage(UiMessage.Plain(message), action))

observeMessageAction(snackbarMessage, onClick)
}

fun addMessage(message: UiMessage) = addMessage(SnackbarMessage<Unit>(message))

private fun addMessage(message: SnackbarMessage<*>) {
Expand Down Expand Up @@ -65,7 +78,7 @@ class SnackbarManager @Inject constructor() {
val result = merge(
actionDismissedMessageChannel.receiveAsFlow().filter { it == message }
.map { null }, // map to null because it's dismissed
actionPerformedMessageChannel.receiveAsFlow().filter { it == message },
actionPerformed.filter { it == message },
).firstOrNull()
return if (result == message) message else null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ fun SnackbarMessagesHost(
}
}


@Composable
private fun <T> CollectEvent(
flow: Flow<T>,
Expand All @@ -46,4 +45,4 @@ private fun <T> CollectEvent(
collector(it)
}
}
}
}

0 comments on commit c33938b

Please sign in to comment.