Skip to content

Commit c793ac5

Browse files
committed
Add splash screen
1 parent c40c1ae commit c793ac5

File tree

12 files changed

+250
-15
lines changed

12 files changed

+250
-15
lines changed

android/app/src/debug/AndroidManifest.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
tools:ignore="ScopedStorage" />
99
<application android:icon="@mipmap/ic_launcher"
1010
android:roundIcon="@mipmap/ic_launcher"
11-
android:theme="@style/AppTheme"
11+
android:theme="@style/Theme.App.Starting"
1212
android:extractNativeLibs="true"
1313
android:allowBackup="false"
1414
android:banner="@drawable/banner"
1515
android:name=".MullvadApplication"
16-
tools:ignore="DataExtractionRules,GoogleAppIndexingWarning"></application>
16+
tools:ignore="DataExtractionRules,GoogleAppIndexingWarning"/>
1717
</manifest>

android/app/src/main/AndroidManifest.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
android:icon="@mipmap/ic_launcher"
2727
android:label="@string/app_name"
2828
android:roundIcon="@mipmap/ic_launcher"
29-
android:theme="@style/AppTheme"
29+
android:theme="@style/Theme.App.Starting"
3030
tools:ignore="GoogleAppIndexingWarning">
3131
<!--
3232
MainActivity

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplashScreen.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import com.ramcosta.composedestinations.generated.destinations.PrivacyDisclaimer
2929
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
3030
import net.mullvad.mullvadvpn.R
3131
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
32-
import net.mullvad.mullvadvpn.compose.transitions.DefaultTransition
3332
import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle
3433
import net.mullvad.mullvadvpn.lib.theme.AppTheme
3534
import net.mullvad.mullvadvpn.lib.theme.Dimens
@@ -45,7 +44,7 @@ private fun PreviewLoadingScreen() {
4544
}
4645

4746
// Set this as the start destination of the default nav graph
48-
@Destination<RootGraph>(start = true, style = DefaultTransition::class)
47+
@Destination<RootGraph>(start = true)
4948
@Composable
5049
fun Splash(navigator: DestinationsNavigator) {
5150
val viewModel: SplashViewModel = koinViewModel()

android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
2626
import net.mullvad.mullvadvpn.repository.RelayListRepository
2727
import net.mullvad.mullvadvpn.repository.RelayOverridesRepository
2828
import net.mullvad.mullvadvpn.repository.SettingsRepository
29+
import net.mullvad.mullvadvpn.repository.SplashCompleteRepository
2930
import net.mullvad.mullvadvpn.repository.SplitTunnelingRepository
3031
import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository
3132
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
@@ -124,6 +125,7 @@ val uiModule = module {
124125
single { SplitTunnelingRepository(get()) }
125126
single { ApiAccessRepository(get()) }
126127
single { NewDeviceRepository() }
128+
single { SplashCompleteRepository() }
127129

128130
single { AccountExpiryNotificationUseCase(get()) }
129131
single { TunnelStateNotificationUseCase(get()) }
@@ -188,7 +190,7 @@ val uiModule = module {
188190
viewModel { PrivacyDisclaimerViewModel(get(), IS_PLAY_BUILD) }
189191
viewModel { SelectLocationViewModel(get(), get(), get(), get(), get(), get()) }
190192
viewModel { SettingsViewModel(get(), get(), IS_PLAY_BUILD) }
191-
viewModel { SplashViewModel(get(), get(), get()) }
193+
viewModel { SplashViewModel(get(), get(), get(), get()) }
192194
viewModel { VoucherDialogViewModel(get()) }
193195
viewModel { VpnSettingsViewModel(get(), get(), get()) }
194196
viewModel { WelcomeViewModel(get(), get(), get(), get(), isPlayBuild = IS_PLAY_BUILD) }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package net.mullvad.mullvadvpn.repository
2+
3+
class SplashCompleteRepository {
4+
private var splashComplete = false
5+
6+
fun isSplashComplete() = splashComplete
7+
8+
fun onSplashCompleted() {
9+
splashComplete = true
10+
}
11+
}

android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.os.Bundle
55
import androidx.activity.ComponentActivity
66
import androidx.activity.compose.setContent
77
import androidx.activity.result.contract.ActivityResultContracts
8+
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
89
import androidx.core.view.WindowCompat
910
import androidx.lifecycle.Lifecycle
1011
import androidx.lifecycle.lifecycleScope
@@ -17,6 +18,7 @@ import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.requestNotificationPermis
1718
import net.mullvad.mullvadvpn.lib.intent.IntentProvider
1819
import net.mullvad.mullvadvpn.lib.theme.AppTheme
1920
import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
21+
import net.mullvad.mullvadvpn.repository.SplashCompleteRepository
2022
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
2123
import net.mullvad.mullvadvpn.viewmodel.NoDaemonViewModel
2224
import org.koin.android.ext.android.getKoin
@@ -31,6 +33,8 @@ class MainActivity : ComponentActivity() {
3133

3234
private lateinit var privacyDisclaimerRepository: PrivacyDisclaimerRepository
3335
private lateinit var serviceConnectionManager: ServiceConnectionManager
36+
private lateinit var splashCompleteRepository: SplashCompleteRepository
37+
private var isReadyNextDraw: Boolean = false
3438
private lateinit var noDaemonViewModel: NoDaemonViewModel
3539
private lateinit var intentProvider: IntentProvider
3640

@@ -45,16 +49,21 @@ class MainActivity : ComponentActivity() {
4549
serviceConnectionManager = get()
4650
noDaemonViewModel = get()
4751
intentProvider = get()
52+
splashCompleteRepository = get()
4853
}
4954
lifecycle.addObserver(noDaemonViewModel)
5055

56+
installSplashScreen().setKeepOnScreenCondition {
57+
val isReady = isReadyNextDraw
58+
isReadyNextDraw = splashCompleteRepository.isSplashComplete()
59+
!isReady
60+
}
5161
super.onCreate(savedInstanceState)
5262

5363
// Needs to be before set content since we want to access the intent in compose
5464
if (savedInstanceState == null) {
5565
intentProvider.setStartIntent(intent)
5666
}
57-
5867
setContent { AppTheme { MullvadApp() } }
5968

6069
// This is to protect against tapjacking attacks

android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SplashViewModel.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package net.mullvad.mullvadvpn.viewmodel
33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
55
import kotlinx.coroutines.async
6+
import kotlinx.coroutines.flow.MutableStateFlow
7+
import kotlinx.coroutines.flow.StateFlow
68
import kotlinx.coroutines.flow.filterNotNull
79
import kotlinx.coroutines.flow.first
810
import kotlinx.coroutines.flow.flow
@@ -14,14 +16,24 @@ import net.mullvad.mullvadvpn.lib.model.DeviceState
1416
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
1517
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
1618
import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
19+
import net.mullvad.mullvadvpn.repository.SplashCompleteRepository
20+
21+
data class SplashScreenState(val splashComplete: Boolean = false)
1722

1823
class SplashViewModel(
1924
private val privacyDisclaimerRepository: PrivacyDisclaimerRepository,
2025
private val accountRepository: AccountRepository,
2126
private val deviceRepository: DeviceRepository,
27+
private val splashCompleteRepository: SplashCompleteRepository
2228
) : ViewModel() {
2329

24-
val uiSideEffect = flow { emit(getStartDestination()) }
30+
val uiSideEffect = flow {
31+
emit(getStartDestination())
32+
splashCompleteRepository.onSplashCompleted()
33+
}
34+
35+
private val _uiState = MutableStateFlow<SplashScreenState>(SplashScreenState(false))
36+
val uiState: StateFlow<SplashScreenState> = _uiState
2537

2638
private suspend fun getStartDestination(): SplashUiSideEffect {
2739
if (!privacyDisclaimerRepository.hasAcceptedPrivacyDisclosure()) {

android/lib/resource/build.gradle.kts

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ android {
2525
}
2626
}
2727

28-
dependencies { implementation(Dependencies.AndroidX.appcompat) }
28+
dependencies {
29+
implementation(Dependencies.AndroidX.appcompat)
30+
implementation(Dependencies.AndroidX.coreSplashscreen)
31+
}

0 commit comments

Comments
 (0)