summaryrefslogtreecommitdiffhomepage
path: root/android/src/main
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-06-17 20:08:51 +0000
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-07-01 19:53:27 +0000
commit8d89c64bfb12dcfe4b2bd334b1684bb955be38d1 (patch)
tree35e60c3dc6fc75b74d405a60facc966a2fac3f2d /android/src/main
parent4c79dd1bbdfbe7ecfe7b6e1f5f3e4c0212d53188 (diff)
downloadmullvadvpn-8d89c64bfb12dcfe4b2bd334b1684bb955be38d1.tar.xz
mullvadvpn-8d89c64bfb12dcfe4b2bd334b1684bb955be38d1.zip
Restore spinner animation while loading relay list
Diffstat (limited to 'android/src/main')
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SelectLocationFragment.kt96
-rw-r--r--android/src/main/res/layout/select_location_header.xml9
2 files changed, 101 insertions, 4 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SelectLocationFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SelectLocationFragment.kt
index ca0e007025..1ba8948375 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SelectLocationFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/SelectLocationFragment.kt
@@ -3,11 +3,14 @@ package net.mullvad.mullvadvpn.ui
import android.content.Context
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
-import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.animation.Animation
+import android.view.animation.Animation.AnimationListener
+import android.view.animation.AnimationUtils
import android.widget.ImageButton
+import kotlinx.coroutines.CompletableDeferred
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.model.Constraint
import net.mullvad.mullvadvpn.model.KeygenEvent
@@ -18,12 +21,22 @@ import net.mullvad.mullvadvpn.relaylist.RelayItem
import net.mullvad.mullvadvpn.relaylist.RelayItemDividerDecoration
import net.mullvad.mullvadvpn.relaylist.RelayList
import net.mullvad.mullvadvpn.relaylist.RelayListAdapter
+import net.mullvad.mullvadvpn.ui.widget.CustomRecyclerView
import net.mullvad.mullvadvpn.util.AdapterWithHeader
class SelectLocationFragment : ServiceDependentFragment(OnNoService.GoToLaunchScreen) {
+ private enum class RelayListState {
+ Initializing,
+ Loading,
+ Visible,
+ }
+
private lateinit var relayListAdapter: RelayListAdapter
private lateinit var titleController: CollapsibleTitleController
+ private var loadingSpinner = CompletableDeferred<View>()
+ private var relayListState = RelayListState.Initializing
+
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -52,11 +65,12 @@ class SelectLocationFragment : ServiceDependentFragment(OnNoService.GoToLaunchSc
titleController = CollapsibleTitleController(view, R.id.relay_list)
- view.findViewById<RecyclerView>(R.id.relay_list).apply {
+ view.findViewById<CustomRecyclerView>(R.id.relay_list).apply {
layoutManager = LinearLayoutManager(parentActivity)
adapter = AdapterWithHeader(relayListAdapter, R.layout.select_location_header).apply {
onHeaderAvailable = { headerView ->
+ initializeLoadingSpinner(headerView)
titleController.expandedTitleView = headerView.findViewById(R.id.expanded_title)
}
}
@@ -68,11 +82,41 @@ class SelectLocationFragment : ServiceDependentFragment(OnNoService.GoToLaunchSc
}
override fun onSafelyResume() {
+ // If the relay list is immediately available, setting the listener will cause it to be
+ // called right away, while the state is still Initializing. In that case we can skip
+ // showing the spinner animation and go directly to the Visible state.
+ //
+ // If it's not immediately available, then when the listener is called later the state will
+ // have changed to Loading, and an animation from the spinner to the new relay items will be
+ // shown.
+ //
+ // If the state is ready, it means that the relay list has already been shown, and we can
+ // update it in place.
relayListListener.onRelayListChange = { relayList, selectedItem ->
- jobTracker.newUiJob("updateRelayList") {
- updateRelayList(relayList, selectedItem)
+ when (relayListState) {
+ RelayListState.Initializing -> {
+ jobTracker.newUiJob("updateRelayList") {
+ updateRelayList(relayList, selectedItem)
+ }
+
+ relayListState = RelayListState.Visible
+ }
+ RelayListState.Loading -> {
+ jobTracker.newUiJob("updateRelayList") {
+ animateRelayListInitialization(relayList, selectedItem)
+ }
+ }
+ RelayListState.Visible -> {
+ jobTracker.newUiJob("updateRelayList") {
+ updateRelayList(relayList, selectedItem)
+ }
+ }
}
}
+
+ if (relayListState == RelayListState.Initializing) {
+ relayListState = RelayListState.Loading
+ }
}
override fun onSafelyPause() {
@@ -106,4 +150,48 @@ class SelectLocationFragment : ServiceDependentFragment(OnNoService.GoToLaunchSc
connectionProxy.connect()
}
}
+
+ private fun initializeLoadingSpinner(parentView: View) {
+ val spinner = parentView.findViewById<View>(R.id.loading_spinner)
+
+ if (relayListState == RelayListState.Visible) {
+ // Because this method is executed inside a layout pass, hiding the spinner needs to be
+ // done in a new job so that it is executed after the layout pass finishes and can
+ // therefore schedule a new layout
+ jobTracker.newUiJob("hideLoadingSpinner") {
+ spinner.visibility = View.GONE
+ }
+ }
+
+ loadingSpinner.complete(spinner)
+ }
+
+ // Smoothly fade out the spinner before showing the relay list items.
+ private suspend fun animateRelayListInitialization(
+ relayList: RelayList,
+ selectedItem: RelayItem?
+ ) {
+ val animationFinished = CompletableDeferred<Unit>()
+ val animationListener = object : AnimationListener {
+ override fun onAnimationEnd(animation: Animation) {
+ animationFinished.complete(Unit)
+ }
+
+ override fun onAnimationStart(animation: Animation) {}
+ override fun onAnimationRepeat(animation: Animation) {}
+ }
+
+ val fadeOut = AnimationUtils.loadAnimation(parentActivity, R.anim.fade_out).apply {
+ setAnimationListener(animationListener)
+ }
+
+ loadingSpinner.await().let { spinner ->
+ spinner.startAnimation(fadeOut)
+
+ animationFinished.await()
+
+ spinner.visibility = View.GONE
+ updateRelayList(relayList, selectedItem)
+ }
+ }
}
diff --git a/android/src/main/res/layout/select_location_header.xml b/android/src/main/res/layout/select_location_header.xml
index 8182db4be0..85b160ad9f 100644
--- a/android/src/main/res/layout/select_location_header.xml
+++ b/android/src/main/res/layout/select_location_header.xml
@@ -21,4 +21,13 @@
android:textColor="@color/white60"
android:textSize="13sp"
android:text="@string/select_location_description" />
+ <ProgressBar android:id="@+id/loading_spinner"
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:layout_gravity="center"
+ android:indeterminate="true"
+ android:indeterminateOnly="true"
+ android:indeterminateDuration="600"
+ android:indeterminateDrawable="@drawable/icon_spinner"
+ android:visibility="visible" />
</LinearLayout>