diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc5d8fdf..23372cbe 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -56,22 +56,16 @@ android:exported="false" /> + android:exported="true" > - - diff --git a/app/src/main/java/akio/apps/myrun/feature/main/MainActivity.kt b/app/src/main/java/akio/apps/myrun/feature/main/MainActivity.kt index 8cd52511..b9a7cd84 100644 --- a/app/src/main/java/akio/apps/myrun/feature/main/MainActivity.kt +++ b/app/src/main/java/akio/apps/myrun/feature/main/MainActivity.kt @@ -2,8 +2,15 @@ package akio.apps.myrun.feature.main import akio.apps.myrun.R import akio.apps.myrun.data.activity.api.model.BaseActivityModel +import akio.apps.myrun.data.authentication.api.model.SignInSuccessResult import akio.apps.myrun.feature.activitydetail.ActivityExportService +import akio.apps.myrun.feature.core.DialogDelegate +import akio.apps.myrun.feature.core.ktx.getParcelableExtraExt +import akio.apps.myrun.feature.core.ktx.lazyViewModelProvider +import akio.apps.myrun.feature.core.navigation.OnBoardingNavigation +import akio.apps.myrun.feature.main.di.DaggerMainActivityComponent import akio.apps.myrun.feature.main.ui.MainNavHost +import akio.apps.myrun.feature.registration.SignInActivity import akio.apps.myrun.feature.route.RoutePlanningFacade import akio.apps.myrun.feature.tracking.LocationPermissionChecker import akio.apps.myrun.feature.tracking.RouteTrackingActivity @@ -14,23 +21,85 @@ import android.widget.Toast import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.splashscreen.SplashScreen +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import timber.log.Timber class MainActivity : AppCompatActivity() { private val locationPermissionChecker: LocationPermissionChecker = LocationPermissionChecker(activity = this) + private val mainViewModel: MainViewModel by lazyViewModelProvider { + DaggerMainActivityComponent.factory().create(application).mainViewModel() + } + + private val dialogDelegate by lazy { DialogDelegate(this) } + + private lateinit var splashScreen: SplashScreen + private var splashStartTime: Long = 0 + override fun onCreate(savedInstanceState: Bundle?) { + splashScreen = installSplashScreen().apply { + setKeepOnScreenCondition { true } + } + splashStartTime = System.currentTimeMillis() super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) + + lifecycleScope.launch { + mainViewModel.isUserSignedIn.collect(::onUserSignIn) + } + dialogDelegate.collectLaunchCatchingError(this, mainViewModel) + } + + @Suppress("DEPRECATION") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + RC_SIGN_IN -> verifySignInResult(resultCode, data) + } + } + + private fun verifySignInResult(resultCode: Int, data: Intent?) { + if (resultCode == RESULT_CANCELED || data == null) { + finish() + return + } + + data.getParcelableExtraExt(SignInActivity.RESULT_SIGN_RESULT_DATA) + ?: return + + goHome() + } + + private fun onUserSignIn(isSignedIn: Boolean) = lifecycleScope.launch { + Timber.d("onUserSignIn $isSignedIn") + val delayMore = SPLASH_MIN_SHOWTIME - (System.currentTimeMillis() - splashStartTime) + if (delayMore > 0) { + delay(delayMore) + } + if (isSignedIn) { + splashScreen.setKeepOnScreenCondition { false } + goHome() + } else { + val intent = OnBoardingNavigation.createSignInIntent(this@MainActivity) + ?: return@launch + @Suppress("DEPRECATION") + startActivityForResult(intent, RC_SIGN_IN) + } + } + + private fun goHome() { setContent { MainNavHost( onClickFloatingActionButton = ::openRouteTrackingOrCheckRequiredPermission, onClickExportActivityFile = ::startActivityExportService - ) { RoutePlanningFacade.startRoutePlanning(this) } + ) { RoutePlanningFacade.startRoutePlanning(this@MainActivity) } } } @@ -73,5 +142,8 @@ class MainActivity : AppCompatActivity() { companion object { fun clearTaskIntent(context: Context): Intent = Intent(context, MainActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + + private const val SPLASH_MIN_SHOWTIME = 700 + private const val RC_SIGN_IN = 1 } } diff --git a/app/src/main/java/akio/apps/myrun/feature/splash/SplashViewModel.kt b/app/src/main/java/akio/apps/myrun/feature/main/MainViewModel.kt similarity index 89% rename from app/src/main/java/akio/apps/myrun/feature/splash/SplashViewModel.kt rename to app/src/main/java/akio/apps/myrun/feature/main/MainViewModel.kt index d5c10a96..492f5bfb 100644 --- a/app/src/main/java/akio/apps/myrun/feature/splash/SplashViewModel.kt +++ b/app/src/main/java/akio/apps/myrun/feature/main/MainViewModel.kt @@ -1,4 +1,4 @@ -package akio.apps.myrun.feature.splash +package akio.apps.myrun.feature.main import akio.apps.myrun.data.authentication.api.UserAuthenticationState import akio.apps.myrun.feature.core.launchcatching.LaunchCatchingDelegate @@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow -class SplashViewModel @Inject constructor( +class MainViewModel @Inject constructor( private val userAuthenticationState: UserAuthenticationState, private val launchCatchingDelegate: LaunchCatchingDelegate, ) : ViewModel(), LaunchCatchingDelegate by launchCatchingDelegate { diff --git a/app/src/main/java/akio/apps/myrun/feature/main/di/HomeTabFeatureComponent.kt b/app/src/main/java/akio/apps/myrun/feature/main/di/HomeTabComponent.kt similarity index 91% rename from app/src/main/java/akio/apps/myrun/feature/main/di/HomeTabFeatureComponent.kt rename to app/src/main/java/akio/apps/myrun/feature/main/di/HomeTabComponent.kt index 4978e1f5..09832857 100644 --- a/app/src/main/java/akio/apps/myrun/feature/main/di/HomeTabFeatureComponent.kt +++ b/app/src/main/java/akio/apps/myrun/feature/main/di/HomeTabComponent.kt @@ -10,7 +10,7 @@ import dagger.Component @FeatureScope @Component(dependencies = [TrackingDataComponent::class]) -interface HomeTabFeatureComponent { +interface HomeTabComponent { fun homeTabViewModel(): HomeTabViewModel @Component.Factory @@ -19,6 +19,6 @@ interface HomeTabFeatureComponent { @BindsInstance application: Application, trackingDataComponent: TrackingDataComponent = DaggerTrackingDataComponent.factory().create(application), - ): HomeTabFeatureComponent + ): HomeTabComponent } } diff --git a/app/src/main/java/akio/apps/myrun/feature/splash/di/SplashFeatureComponent.kt b/app/src/main/java/akio/apps/myrun/feature/main/di/MainActivityComponent.kt similarity index 78% rename from app/src/main/java/akio/apps/myrun/feature/splash/di/SplashFeatureComponent.kt rename to app/src/main/java/akio/apps/myrun/feature/main/di/MainActivityComponent.kt index e96e8582..6530ac36 100644 --- a/app/src/main/java/akio/apps/myrun/feature/splash/di/SplashFeatureComponent.kt +++ b/app/src/main/java/akio/apps/myrun/feature/main/di/MainActivityComponent.kt @@ -1,10 +1,10 @@ -package akio.apps.myrun.feature.splash.di +package akio.apps.myrun.feature.main.di import akio.apps.myrun.base.di.FeatureScope import akio.apps.myrun.data.authentication.di.AuthenticationDataComponent import akio.apps.myrun.data.authentication.di.DaggerAuthenticationDataComponent import akio.apps.myrun.feature.core.launchcatching.LaunchCatchingModule -import akio.apps.myrun.feature.splash.SplashViewModel +import akio.apps.myrun.feature.main.MainViewModel import android.app.Application import dagger.BindsInstance import dagger.Component @@ -16,8 +16,8 @@ import dagger.Component ], dependencies = [AuthenticationDataComponent::class] ) -interface SplashFeatureComponent { - fun splashViewModel(): SplashViewModel +interface MainActivityComponent { + fun mainViewModel(): MainViewModel @Component.Factory interface Factory { @@ -25,6 +25,6 @@ interface SplashFeatureComponent { @BindsInstance application: Application, authenticationDataComponent: AuthenticationDataComponent = DaggerAuthenticationDataComponent.factory().create(application), - ): SplashFeatureComponent + ): MainActivityComponent } } diff --git a/app/src/main/java/akio/apps/myrun/feature/main/ui/HomeTabComposable.kt b/app/src/main/java/akio/apps/myrun/feature/main/ui/HomeTabComposable.kt index c31de1cb..a46e1a2a 100644 --- a/app/src/main/java/akio/apps/myrun/feature/main/ui/HomeTabComposable.kt +++ b/app/src/main/java/akio/apps/myrun/feature/main/ui/HomeTabComposable.kt @@ -11,7 +11,7 @@ import akio.apps.myrun.feature.core.ui.AppTheme import akio.apps.myrun.feature.core.ui.NavigationBarSpacer import akio.apps.myrun.feature.feed.ui.ActivityFeedComposable import akio.apps.myrun.feature.main.HomeTabViewModel -import akio.apps.myrun.feature.main.di.DaggerHomeTabFeatureComponent +import akio.apps.myrun.feature.main.di.DaggerHomeTabComponent import akio.apps.myrun.feature.userstats.ui.CurrentUserStatsComposable import android.app.Application import androidx.annotation.StringRes @@ -147,7 +147,7 @@ fun HomeTabComposable( private fun rememberViewModel(): HomeTabViewModel { val application = LocalContext.current.applicationContext as Application return remember { - DaggerHomeTabFeatureComponent.factory().create(application).homeTabViewModel() + DaggerHomeTabComponent.factory().create(application).homeTabViewModel() } } diff --git a/app/src/main/java/akio/apps/myrun/feature/splash/SplashActivity.kt b/app/src/main/java/akio/apps/myrun/feature/splash/SplashActivity.kt deleted file mode 100644 index b5d5293f..00000000 --- a/app/src/main/java/akio/apps/myrun/feature/splash/SplashActivity.kt +++ /dev/null @@ -1,90 +0,0 @@ -package akio.apps.myrun.feature.splash - -import akio.apps.myrun.data.authentication.api.model.SignInSuccessResult -import akio.apps.myrun.feature.core.DialogDelegate -import akio.apps.myrun.feature.core.ktx.collectRepeatOnStarted -import akio.apps.myrun.feature.core.ktx.getParcelableExtraExt -import akio.apps.myrun.feature.core.ktx.lazyViewModelProvider -import akio.apps.myrun.feature.core.navigation.OnBoardingNavigation -import akio.apps.myrun.feature.main.MainActivity -import akio.apps.myrun.feature.registration.SignInActivity -import akio.apps.myrun.feature.splash.di.DaggerSplashFeatureComponent -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import timber.log.Timber - -class SplashActivity : AppCompatActivity() { - - private val splashViewModel: SplashViewModel by lazyViewModelProvider { - DaggerSplashFeatureComponent.factory().create(application).splashViewModel() - } - - private val dialogDelegate by lazy { DialogDelegate(this) } - - private val splashStartTime: Long = System.currentTimeMillis() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - installSplashScreen().setKeepOnScreenCondition { true } - initObservers() - } - - private fun initObservers() { - dialogDelegate.collectLaunchCatchingError(this, splashViewModel) - collectRepeatOnStarted(splashViewModel.isUserSignedIn, ::onUserSignIn) - } - - private fun onUserSignIn(isSignedIn: Boolean) = lifecycleScope.launch { - Timber.d("onUserSignIn $isSignedIn") - val delayMore = SPLASH_MIN_SHOWTIME - (System.currentTimeMillis() - splashStartTime) - if (delayMore > 0) { - delay(delayMore) - } - if (isSignedIn) { - goHome() - } else { - val intent = OnBoardingNavigation.createSignInIntent(this@SplashActivity) - ?: return@launch - @Suppress("DEPRECATION") - startActivityForResult(intent, RC_SIGN_IN) - } - } - - @Suppress("DEPRECATION") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - RC_SIGN_IN -> verifySignInResult(resultCode, data) - RC_ON_BOARDING -> goHome() - } - } - - private fun goHome() { - startActivity(MainActivity.clearTaskIntent(this)) - } - - private fun verifySignInResult(resultCode: Int, data: Intent?) { - if (resultCode == RESULT_CANCELED || data == null) { - finish() - return - } - - data.getParcelableExtraExt(SignInActivity.RESULT_SIGN_RESULT_DATA) - ?: return - - goHome() - } - - companion object { - private const val RC_SIGN_IN = 1 - private const val RC_ON_BOARDING = 2 - - private const val SPLASH_MIN_SHOWTIME = 700 - } -} diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 15f29d62..3997a0ac 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -28,6 +28,6 @@ diff --git a/app/src/test/java/akio/apps/myrun/feature/core/navigation/OnBoardingNavigationTest.kt b/app/src/test/java/akio/apps/myrun/feature/core/navigation/OnBoardingNavigationTest.kt index c7dfb921..15b4fd34 100644 --- a/app/src/test/java/akio/apps/myrun/feature/core/navigation/OnBoardingNavigationTest.kt +++ b/app/src/test/java/akio/apps/myrun/feature/core/navigation/OnBoardingNavigationTest.kt @@ -1,9 +1,7 @@ package akio.apps.myrun.feature.core.navigation import akio.apps.myrun.feature.core.navigation.OnBoardingNavigation.SIGNIN_ACTIVITY_CLASS_NAME -import akio.apps.myrun.feature.core.navigation.OnBoardingNavigation.SPLASH_ACTIVITY_CLASS_NAME import akio.apps.myrun.feature.registration.SignInActivity -import akio.apps.myrun.feature.splash.SplashActivity import kotlin.test.assertEquals import org.junit.Test @@ -15,12 +13,4 @@ class OnBoardingNavigationTest { SignInActivity::class.java.name ) } - - @Test - fun testSplashActivityClassNameChangedCorrectly() { - assertEquals( - SPLASH_ACTIVITY_CLASS_NAME, - SplashActivity::class.java.name - ) - } } diff --git a/app/src/test/java/akio/apps/myrun/feature/splash/SplashViewModelTest.kt b/app/src/test/java/akio/apps/myrun/feature/splash/MainViewModelTest.kt similarity index 81% rename from app/src/test/java/akio/apps/myrun/feature/splash/SplashViewModelTest.kt rename to app/src/test/java/akio/apps/myrun/feature/splash/MainViewModelTest.kt index a2d836c4..26832934 100644 --- a/app/src/test/java/akio/apps/myrun/feature/splash/SplashViewModelTest.kt +++ b/app/src/test/java/akio/apps/myrun/feature/splash/MainViewModelTest.kt @@ -2,6 +2,7 @@ package akio.apps.myrun.feature.splash import akio.apps.myrun.data.authentication.api.UserAuthenticationState import akio.apps.myrun.feature.core.launchcatching.LaunchCatchingDelegate +import akio.apps.myrun.feature.main.MainViewModel import app.cash.turbine.test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -12,9 +13,9 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) -class SplashViewModelTest { +class MainViewModelTest { - private lateinit var splashViewModel: SplashViewModel + private lateinit var mainViewModel: MainViewModel private lateinit var mockedUserAuthState: UserAuthenticationState private lateinit var mockLaunchCatchingDelegate: LaunchCatchingDelegate @@ -30,8 +31,8 @@ class SplashViewModelTest { whenever(mockedUserAuthState.isSignedIn()) .then { throw userAuthStateError } - splashViewModel = SplashViewModel(mockedUserAuthState, mockLaunchCatchingDelegate) - splashViewModel.isUserSignedIn.test { + mainViewModel = MainViewModel(mockedUserAuthState, mockLaunchCatchingDelegate) + mainViewModel.isUserSignedIn.test { awaitComplete() verify(mockLaunchCatchingDelegate).setLaunchCatchingError(userAuthStateError) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f6f2324f..2d499206 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ workmanager = "2.8.0" paging = "3.1.1" room = "2.5.0" lifecycle = "2.5.1" -splashscreen = "1.0.0" +splashscreen = "1.0.1" androidx-annotation = "1.6.0" multidex = "2.0.1" androidx-coretesting = "2.2.0"