Skip to content

Commit

Permalink
Force App Update (#304)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristhianescobar authored Aug 14, 2024
1 parent 19a696e commit 88224f5
Show file tree
Hide file tree
Showing 18 changed files with 200 additions and 87 deletions.
3 changes: 1 addition & 2 deletions android/app-newm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ android {
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 5
versionName = "0.2.5"
versionName = "0.3.0"
testInstrumentationRunner = "io.newm.NewmAndroidJUnitRunner"
testApplicationId = "io.newm.test"
}
Expand Down Expand Up @@ -57,7 +57,6 @@ android {
namespace = "io.newm"
applicationId = "io.newm"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
dimension = "version"
}
}
Expand Down
16 changes: 14 additions & 2 deletions android/app-newm/src/main/java/io/newm/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.core.view.WindowCompat
import com.slack.circuit.foundation.Circuit
import com.slack.circuit.foundation.CircuitCompositionLocals
Expand All @@ -21,20 +23,23 @@ import io.newm.screens.profile.view.ProfileUi
import io.newm.screens.forceupdate.ForceAppUpdatePresenter
import io.newm.screens.forceupdate.ForceAppUpdateState
import io.newm.screens.forceupdate.ForceAppUpdateUi
import io.newm.screens.forceupdate.openAppPlayStore
import io.newm.screens.library.NFTLibraryPresenter
import io.newm.screens.library.NFTLibraryScreenUi
import io.newm.screens.library.NFTLibraryState
import io.newm.screens.profile.edit.ProfileEditPresenter
import io.newm.screens.profile.edit.ProfileEditUi
import io.newm.screens.profile.edit.ProfileEditUiState
import io.newm.shared.NewmAppLogger
import io.newm.utils.ForceAppUpdateViewModel
import io.newm.utils.ui
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf

class HomeActivity : ComponentActivity() {
private val circuit: Circuit = createCircuit()
private val logger: NewmAppLogger by inject()
private val forceAppUpdateViewModel: ForceAppUpdateViewModel by inject()


override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -44,7 +49,14 @@ class HomeActivity : ComponentActivity() {
setContent {
NewmTheme(darkTheme = true) {
CircuitDependencies {
NewmApp(logger)
val updateRequired by forceAppUpdateViewModel.updateRequiredState.collectAsState()
if (updateRequired) {
ForceAppUpdateUi(
ForceAppUpdateState.Content(eventSink = { openAppPlayStore() })
)
} else {
NewmApp(logger)
}
}
}
}
Expand Down Expand Up @@ -139,4 +151,4 @@ class HomeActivity : ComponentActivity() {
}
}
}
}
}
40 changes: 16 additions & 24 deletions android/app-newm/src/main/java/io/newm/LoginActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import com.google.android.recaptcha.Recaptcha
import com.slack.circuit.backstack.rememberSaveableBackStack
import com.slack.circuit.foundation.Circuit
import com.slack.circuit.foundation.CircuitCompositionLocals
Expand All @@ -24,7 +24,6 @@ import io.newm.core.theme.NewmTheme
import io.newm.feature.login.screen.LoginScreen
import io.newm.feature.login.screen.LoginScreenUi
import io.newm.feature.login.screen.ResetPasswordScreen
import io.newm.feature.login.screen.authproviders.RecaptchaClientProvider
import io.newm.feature.login.screen.createaccount.CreateAccountScreen
import io.newm.feature.login.screen.createaccount.CreateAccountScreenPresenter
import io.newm.feature.login.screen.createaccount.CreateAccountUi
Expand All @@ -38,18 +37,19 @@ import io.newm.feature.login.screen.welcome.WelcomeScreenPresenter
import io.newm.feature.login.screen.welcome.WelcomeScreenUi
import io.newm.feature.login.screen.welcome.WelcomeScreenUiState
import io.newm.screens.Screen.LoginLandingScreen
import io.newm.screens.forceupdate.ForceAppUpdateState
import io.newm.screens.forceupdate.ForceAppUpdateUi
import io.newm.screens.forceupdate.openAppPlayStore
import io.newm.shared.NewmAppLogger
import io.newm.shared.config.NewmSharedBuildConfig
import io.newm.utils.ForceAppUpdateViewModel
import io.newm.utils.ui
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf

class LoginActivity : ComponentActivity() {

private val recaptchaClientProvider: RecaptchaClientProvider by inject()
private val buildConfig: NewmSharedBuildConfig by inject()
private val logger: NewmAppLogger by inject()
private val forceAppUpdateViewModel: ForceAppUpdateViewModel by inject()

// TODO inject
private val circuit: Circuit = Circuit.Builder()
Expand Down Expand Up @@ -99,11 +99,18 @@ class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()
initializeRecaptchaClient()
setContent {
NewmTheme(darkTheme = true) {
CircuitDependencies {
WelcomeToNewm(logger, ::launchHomeActivity)
val updateRequired by forceAppUpdateViewModel.updateRequiredState.collectAsState()

if (updateRequired) {
ForceAppUpdateUi(
ForceAppUpdateState.Content(eventSink = { openAppPlayStore()})
)
} else {
WelcomeToNewm(logger, ::launchHomeActivity)
}
}
}
}
Expand All @@ -124,21 +131,6 @@ class LoginActivity : ComponentActivity() {
startActivity(Intent(this@LoginActivity, HomeActivity::class.java))
finish()
}

private fun initializeRecaptchaClient() {
lifecycleScope.launch {
Recaptcha.getClient(application, buildConfig.recaptchaSiteKey, timeout = 20000L)
.onSuccess { client ->
recaptchaClientProvider.setRecaptchaClient(client)
}.onFailure { exception ->
logger.error(
tag = "RecaptchaClient",
message = "Failed to initialize Recaptcha client.",
exception = exception
)
}
}
}
}

@Composable
Expand Down
21 changes: 15 additions & 6 deletions android/app-newm/src/main/java/io/newm/NewmAppComposable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
Expand Down Expand Up @@ -62,7 +65,10 @@ import io.newm.core.ui.utils.iconGradient
import io.newm.feature.musicplayer.MiniPlayer
import io.newm.feature.musicplayer.MusicPlayerScreen
import io.newm.screens.Screen
import io.newm.screens.forceupdate.ForceAppUpdateState
import io.newm.screens.forceupdate.ForceAppUpdateUi
import io.newm.shared.NewmAppLogger
import io.newm.utils.ForceAppUpdateViewModel
import kotlinx.coroutines.launch
import com.slack.circuit.runtime.screen.Screen as CircuitScreen

Expand All @@ -84,7 +90,11 @@ val initialScreen = Screen.NFTLibrary

@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun NewmApp(logger: NewmAppLogger) {
internal fun NewmApp(
logger: NewmAppLogger,
) {

val context = LocalContext.current
val backstack = rememberSaveableBackStack {
push(initialScreen)
}
Expand All @@ -95,10 +105,10 @@ internal fun NewmApp(logger: NewmAppLogger) {
enableBackHandler = false
)

val context = LocalContext.current
val newmNavigator = rememberNewmNavigator(circuitNavigator, logger, {}, launchBrowser = { url ->
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
})
val newmNavigator =
rememberNewmNavigator(circuitNavigator, logger, {}, launchBrowser = { url ->
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
})

val currentRootScreen = backstack.topRecord?.screen

Expand Down Expand Up @@ -173,7 +183,6 @@ internal fun NewmApp(logger: NewmAppLogger) {
}

}

}

@Composable
Expand Down
33 changes: 31 additions & 2 deletions android/app-newm/src/main/java/io/newm/NewmApplication.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
package io.newm

import android.app.Application
import androidx.lifecycle.lifecycleScope
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.google.android.recaptcha.Recaptcha
import io.newm.BuildConfig.*
import io.newm.di.android.androidModules
import io.newm.di.android.viewModule
import io.newm.feature.login.screen.authproviders.RecaptchaClientProvider
import io.newm.shared.NewmAppAnalyticsTracker
import io.newm.shared.NewmAppLogger
import io.newm.shared.config.NewmSharedBuildConfig
import io.newm.shared.di.initKoin
import io.newm.shared.public.usecases.ForceAppUpdateUseCase
import io.newm.utils.AndroidNewmAppAnalyticsTracker
import io.newm.utils.AndroidNewmAppLogger
import io.newm.utils.ForceAppUpdateViewModel
import io.newm.utils.NewmImageLoaderFactory
import io.sentry.Hint
import io.sentry.SentryEvent
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.android.core.SentryAndroid
import io.sentry.android.core.SentryAndroidOptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
Expand All @@ -29,15 +37,36 @@ open class NewmApplication : Application(), ImageLoaderFactory {

private val logout: Logout by inject()
private val logger: NewmAppLogger by inject()
private val buildConfig: NewmSharedBuildConfig by inject()
private val recaptchaClientProvider: RecaptchaClientProvider by inject()
private val analyticsTracker: NewmAppAnalyticsTracker by inject()
private val imageLoaderFactory by lazy { NewmImageLoaderFactory(this) }
private val config: NewmSharedBuildConfig by inject()
private val forceAppUpdateViewModel: ForceAppUpdateViewModel by inject()

override fun onCreate() {
super.onCreate()
initKoin()
logout.register()
bindClientImplementations()
initializeRecaptchaClient()
}

private fun initializeRecaptchaClient() {
CoroutineScope(Dispatchers.Default).launch {
Recaptcha.getClient(this@NewmApplication, buildConfig.recaptchaSiteKey, timeout = 50000L)
.onSuccess { client ->
recaptchaClientProvider.setRecaptchaClient(client)
forceAppUpdateViewModel.checkForUpdates(currentVersion = BuildConfig.VERSION_NAME)

}.onFailure { exception ->
logger.error(
tag = "RecaptchaClient",
message = "Failed to initialize Recaptcha client.",
exception = exception
)
}
}
}

private fun initKoin() {
Expand All @@ -46,8 +75,8 @@ open class NewmApplication : Application(), ImageLoaderFactory {
androidLogger(if (enableLogs) Level.INFO else Level.NONE)
androidContext(this@NewmApplication)
modules(
viewModule,
androidModules
androidModules,
viewModule
)
}
}
Expand Down
16 changes: 12 additions & 4 deletions android/app-newm/src/main/java/io/newm/di/android/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,34 @@ import io.newm.feature.login.screen.welcome.WelcomeScreenPresenter
import io.newm.feature.musicplayer.repository.MockMusicRepository
import io.newm.feature.musicplayer.repository.MusicRepository
import io.newm.feature.musicplayer.viewmodel.MusicPlayerViewModel
import io.newm.screens.home.categories.MusicalCategoriesViewModel
import io.newm.feature.login.screen.authproviders.RecaptchaClientProvider
import io.newm.screens.profile.view.ProfilePresenter
import io.newm.screens.forceupdate.ForceAppUpdatePresenter
import io.newm.screens.library.NFTLibraryPresenter
import io.newm.screens.profile.edit.ProfileEditPresenter
import io.newm.shared.config.NewmSharedBuildConfig
import io.newm.utils.ForceAppUpdateViewModel
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.androidx.viewmodel.dsl.viewModelOf
import org.koin.dsl.module

val viewModule = module {
viewModelOf(::MusicalCategoriesViewModel)
single { ForceAppUpdateViewModel(get(), get()) }
viewModel { params -> MusicPlayerViewModel(params.get(), params.get(), get(), get()) }
single { RecaptchaClientProvider() }

factory { params -> CreateAccountScreenPresenter(params.get(), get(), get(), get(), get()) }
factory { params -> LoginScreenPresenter(params.get(), get(), get(), get()) }
factory { params -> ResetPasswordScreenPresenter(params.get(), get(), get(), get(), get(), get()) }
factory { params ->
ResetPasswordScreenPresenter(
params.get(),
get(),
get(),
get(),
get(),
get()
)
}
single<GoogleSignInLauncher> {
val sharedBuildConfig = get<NewmSharedBuildConfig>()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.newm.screens.forceupdate

import android.content.Context
import android.content.Intent
import android.net.Uri

fun Context.openAppPlayStore() {
// Build the Play Store URL using the package name
val appPackageName = packageName // Current app's package name
val playStoreUrl = "https://play.google.com/store/apps/details?id=$appPackageName"

try {
// Attempt to open the Play Store app
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(playStoreUrl)))
} catch (e: Exception) {
// If Play Store app is not available, open in the web browser
val webPlayStoreUrl = "https://play.google.com/store/apps/details?id=$appPackageName"
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(webPlayStoreUrl)))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.newm.utils

import androidx.lifecycle.ViewModel
import com.google.android.recaptcha.RecaptchaAction
import io.newm.feature.login.screen.authproviders.RecaptchaClientProvider
import io.newm.shared.NewmAppLogger
import io.newm.shared.public.usecases.ForceAppUpdateUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext


class ForceAppUpdateViewModel(
private val forceAppUpdateUseCase: ForceAppUpdateUseCase,
private val recaptchaClientProvider: RecaptchaClientProvider,
) : ViewModel() {
private val _updateRequiredState = MutableStateFlow(false)
val updateRequiredState: StateFlow<Boolean> get() = _updateRequiredState

suspend fun checkForUpdates(currentVersion: String) = withContext(Dispatchers.IO) {
try {
val recaptchaClient = recaptchaClientProvider.get()
recaptchaClient.execute(RecaptchaAction.custom("mobile_config"))
.onSuccess { token ->
val updateRequired = forceAppUpdateUseCase.isAndroidUpdateRequired(currentVersion, token)
_updateRequiredState.value = updateRequired
}
.onFailure {
_updateRequiredState.value = false
}
} catch (e: Exception) {
_updateRequiredState.value = false
}
}
}
Loading

0 comments on commit 88224f5

Please sign in to comment.