diff options
| author | Albin <albin@mullvad.net> | 2022-06-13 08:54:03 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2022-06-15 10:25:32 +0200 |
| commit | d184669f0f932f08dd9c8f45d3d241e10daa77f4 (patch) | |
| tree | 607085a627b0e3d7c25dbe662aefbc62b04c901e /android | |
| parent | d4bc0265f16b15af130c393b5da523097fcc6efe (diff) | |
| download | mullvadvpn-d184669f0f932f08dd9c8f45d3d241e10daa77f4.tar.xz mullvadvpn-d184669f0f932f08dd9c8f45d3d241e10daa77f4.zip | |
Improve state handling
Diffstat (limited to 'android')
5 files changed, 97 insertions, 68 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt index 567f5f233f..7745d13214 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt @@ -5,7 +5,10 @@ import kotlinx.parcelize.Parcelize sealed class DeviceState : Parcelable { @Parcelize - object InitialState : DeviceState() + object Initial : DeviceState() + + @Parcelize + object Unknown : DeviceState() @Parcelize class LoggedIn(val accountAndDevice: AccountAndDevice) : DeviceState() @@ -16,8 +19,8 @@ sealed class DeviceState : Parcelable { @Parcelize object Revoked : DeviceState() - fun isInitialState(): Boolean { - return this is InitialState + fun isUnknown(): Boolean { + return this is Unknown } fun deviceName(): String? { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt index 420581785b..4699576169 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt @@ -4,20 +4,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.flowWithLifecycle -import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.model.DeviceState -import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository -import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer -import org.koin.android.ext.android.inject - -class LaunchFragment : ServiceAwareFragment() { - private val deviceRepository: DeviceRepository by inject() +class LaunchFragment : Fragment(), StatusBarPainter, NavigationBarPainter { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -26,51 +17,16 @@ class LaunchFragment : ServiceAwareFragment() { val view = inflater.inflate(R.layout.launch, container, false) view.findViewById<View>(R.id.settings).setOnClickListener { - parentActivity.openSettings() + (context as? MainActivity)?.openSettings() } - return view - } - - override fun onNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer) { - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - lifecycleScope.launch { - deviceRepository.deviceState - .flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED) - .collect { deviceState -> - when (deviceState) { - is DeviceState.LoggedIn -> advanceToConnectScreen() - is DeviceState.LoggedOut -> advanceToLoginScreen() - is DeviceState.Revoked -> advanceToRevokedScreen() - else -> Unit - } - } - } - } + context + ?.let { ContextCompat.getColor(it, R.color.blue) } + ?.let { color -> + paintStatusBar(color) + paintNavigationBar(color) + } - private fun advanceToLoginScreen() { - parentFragmentManager.beginTransaction().apply { - replace(R.id.main_fragment, LoginFragment()) - commit() - } - } - - private fun advanceToConnectScreen() { - parentFragmentManager.beginTransaction().apply { - replace(R.id.main_fragment, ConnectFragment()) - commit() - } - } - - private fun advanceToRevokedScreen() { - // TODO: Open revoked screen. - parentFragmentManager.beginTransaction().apply { - replace(R.id.main_fragment, LoginFragment()) - commit() - } + return view } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt index cb39336651..56533d479a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt @@ -12,15 +12,28 @@ import android.view.WindowManager import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.BuildConfig import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport import net.mullvad.mullvadvpn.di.uiModule +import net.mullvad.mullvadvpn.model.DeviceState +import net.mullvad.mullvadvpn.ui.fragments.DeviceRevokedFragment +import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import org.koin.android.ext.android.getKoin import org.koin.core.context.loadKoinModules open class MainActivity : FragmentActivity() { + private val deviceRepository: DeviceRepository by inject() val problemReport = MullvadProblemReport() private var visibleSecureScreens = HashSet<Fragment>() @@ -54,9 +67,7 @@ open class MainActivity : FragmentActivity() { setContentView(R.layout.main) - if (savedInstanceState == null) { - addInitialFragment() - } + launchDeviceStateHandler() } override fun onStart() { @@ -136,6 +147,43 @@ open class MainActivity : FragmentActivity() { } } + private fun launchDeviceStateHandler() { + var currentState: DeviceState? = null + + lifecycleScope.launch { + deviceRepository.deviceState + .flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED) + .debounce { + // Debounce DeviceState.Unknown to delay view transitions during reconnect. + it.addDebounceForUnknownState() + } + .distinctByDeviceState() + .filter { newState -> newState != currentState } + .collect { newState -> + when (newState) { + is DeviceState.Initial, + is DeviceState.Unknown -> openLaunchView() + is DeviceState.LoggedOut -> openLoginView() + is DeviceState.Revoked -> openLoginView() + is DeviceState.LoggedIn -> openConnectView() + } + currentState = newState + } + } + } + + private fun Flow<DeviceState>.distinctByDeviceState(): Flow<DeviceState> { + return this.distinctUntilChangedBy { it::class } + } + + private fun DeviceState.addDebounceForUnknownState(): Long { + return if (this is DeviceState.Unknown) { + UNKNOWN_STATE_DEBOUNCE_DELAY_MILLISECONDS + } else { + ZERO_DEBOUNCE_DELAY_MILLISECONDS + } + } + @Suppress("DEPRECATION") private fun requestVpnPermission() { val intent = VpnService.prepare(this) @@ -143,10 +191,29 @@ open class MainActivity : FragmentActivity() { startActivityForResult(intent, 0) } - private fun addInitialFragment() { + private fun openLaunchView() { supportFragmentManager.beginTransaction().apply { - add(R.id.main_fragment, LaunchFragment()) + replace(R.id.main_fragment, LaunchFragment()) commit() } } + + private fun openConnectView() { + supportFragmentManager.beginTransaction().apply { + replace(R.id.main_fragment, ConnectFragment()) + commit() + } + } + + private fun openLoginView() { + supportFragmentManager.beginTransaction().apply { + replace(R.id.main_fragment, LoginFragment()) + commit() + } + } + + companion object { + private const val ZERO_DEBOUNCE_DELAY_MILLISECONDS = 0L + private const val UNKNOWN_STATE_DEBOUNCE_DELAY_MILLISECONDS = 2000L + } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt index f2a6fe7ff1..9975b48ef9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt @@ -1,16 +1,18 @@ package net.mullvad.mullvadvpn.ui.serviceconnection +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import net.mullvad.mullvadvpn.model.DeviceState class DeviceRepository( - private val serviceConnectionManager: ServiceConnectionManager + private val serviceConnectionManager: ServiceConnectionManager, + dispatcher: CoroutineDispatcher = Dispatchers.IO ) { val deviceState = serviceConnectionManager.connectionState .flatMapLatest { state -> @@ -20,10 +22,10 @@ class DeviceRepository( state.container.deviceDataSource.refreshDevice() } } else { - emptyFlow() + flowOf(DeviceState.Unknown) } } - .stateIn(CoroutineScope(Dispatchers.IO), SharingStarted.Lazily, DeviceState.InitialState) + .stateIn(CoroutineScope(dispatcher), SharingStarted.Lazily, DeviceState.Initial) fun refreshDeviceState() { container()?.deviceDataSource?.refreshDevice() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt index 60a2eb68e9..23892257d6 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt @@ -12,9 +12,10 @@ class ServiceConnectionDeviceDataSource( private val dispatcher: EventDispatcher ) { val deviceStateUpdates = callbackFlow { - dispatcher.registerHandler(Event.DeviceStateEvent::class) { event -> + val handler: (Event.DeviceStateEvent) -> Unit = { event -> trySend(event.newState) } + dispatcher.registerHandler(Event.DeviceStateEvent::class, handler) awaitClose { // The current dispatcher doesn't support unregistration of handlers. } |
