summaryrefslogtreecommitdiffhomepage
path: root/android/app
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2022-06-13 08:54:03 +0200
committerAlbin <albin@mullvad.net>2022-06-15 10:25:32 +0200
commitd184669f0f932f08dd9c8f45d3d241e10daa77f4 (patch)
tree607085a627b0e3d7c25dbe662aefbc62b04c901e /android/app
parentd4bc0265f16b15af130c393b5da523097fcc6efe (diff)
downloadmullvadvpn-d184669f0f932f08dd9c8f45d3d241e10daa77f4.tar.xz
mullvadvpn-d184669f0f932f08dd9c8f45d3d241e10daa77f4.zip
Improve state handling
Diffstat (limited to 'android/app')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt66
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt77
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt3
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.
}