summaryrefslogtreecommitdiffhomepage
path: root/android/src
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-03-16 20:07:02 +0000
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-03-18 17:03:54 +0000
commit88212cb101afbd45ad8e78d53781c620e2a5e463 (patch)
tree94732b76f42d8d07d85aa4bf5d3cf3e4777b4dd6 /android/src
parentcdc6e80d840e11cc75d3daa4929b2d4dc1c42e8b (diff)
downloadmullvadvpn-88212cb101afbd45ad8e78d53781c620e2a5e463.tar.xz
mullvadvpn-88212cb101afbd45ad8e78d53781c620e2a5e463.zip
Implement relay list item expansion and collapse
Diffstat (limited to 'android/src')
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/Relay.kt11
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCity.kt25
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCountry.kt29
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt12
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemHolder.kt35
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt92
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapterPosition.kt3
-rw-r--r--android/src/main/res/drawable/icon_chevron_expand.xml9
-rw-r--r--android/src/main/res/layout/relay_list_item.xml7
9 files changed, 181 insertions, 42 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/Relay.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/Relay.kt
index 47a17c69d3..9335899ef6 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/Relay.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/Relay.kt
@@ -1,3 +1,12 @@
package net.mullvad.mullvadvpn.relaylist
-data class Relay(val hostname: String)
+data class Relay(override val name: String) : RelayItem {
+ override val type = RelayItemType.Relay
+ override val hasChildren = false
+
+ override val visibleChildCount = 0
+
+ override var expanded
+ get() = false
+ set(value) {}
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCity.kt
index 5ccd78bde9..b9d1bddd69 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCity.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCity.kt
@@ -1,9 +1,26 @@
package net.mullvad.mullvadvpn.relaylist
-class RelayCity(val city: String, val relays: List<Relay>, var expanded: Boolean) {
+class RelayCity(
+ override val name: String,
+ override var expanded: Boolean,
+ val relays: List<Relay>
+) : RelayItem {
+ override val type = RelayItemType.City
+ override val hasChildren
+ get() = relays.size > 1
+
+ override val visibleChildCount: Int
+ get() {
+ if (expanded) {
+ return relays.size
+ } else {
+ return 0
+ }
+ }
+
fun getItem(position: Int): GetItemResult {
if (position == 0) {
- return GetItemResult.Item(RelayItem(RelayItemType.City, city))
+ return GetItemResult.Item(this)
}
if (!expanded) {
@@ -16,7 +33,7 @@ class RelayCity(val city: String, val relays: List<Relay>, var expanded: Boolean
if (offset >= relayCount) {
return GetItemResult.Count(1 + relayCount)
} else {
- return GetItemResult.Item(RelayItem(RelayItemType.Relay, relays[offset].hostname))
+ return GetItemResult.Item(relays[offset])
}
}
@@ -27,4 +44,6 @@ class RelayCity(val city: String, val relays: List<Relay>, var expanded: Boolean
return 1
}
}
+
+ fun getRelayCount(): Int = relays.size
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCountry.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCountry.kt
index 6f54f50518..17b18da007 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCountry.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCountry.kt
@@ -1,9 +1,26 @@
package net.mullvad.mullvadvpn.relaylist
-class RelayCountry(val country: String, val cities: List<RelayCity>, var expanded: Boolean) {
+class RelayCountry(
+ override val name: String,
+ override var expanded: Boolean,
+ val cities: List<RelayCity>
+) : RelayItem {
+ override val type = RelayItemType.Country
+ override val hasChildren
+ get() = getRelayCount() > 1
+
+ override val visibleChildCount: Int
+ get() {
+ if (expanded) {
+ return cities.map { city -> city.visibleItemCount }.sum()
+ } else {
+ return 0
+ }
+ }
+
fun getItem(position: Int): GetItemResult {
if (position == 0) {
- return GetItemResult.Item(RelayItem(RelayItemType.Country, country))
+ return GetItemResult.Item(this)
}
var itemCount = 1
@@ -26,11 +43,5 @@ class RelayCountry(val country: String, val cities: List<RelayCity>, var expande
return GetItemResult.Count(itemCount)
}
- fun getItemCount(): Int {
- if (expanded) {
- return 1 + cities.map { city -> city.getItemCount() }.sum()
- } else {
- return 1
- }
- }
+ fun getRelayCount(): Int = cities.map { city -> city.getRelayCount() }.sum()
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt
index 4814df43db..24329e18ed 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt
@@ -1,3 +1,13 @@
package net.mullvad.mullvadvpn.relaylist
-data class RelayItem(val type: RelayItemType, val name: String)
+interface RelayItem {
+ val type: RelayItemType
+ val name: String
+ val hasChildren: Boolean
+ val visibleChildCount: Int
+
+ val visibleItemCount: Int
+ get() = visibleChildCount + 1
+
+ var expanded: Boolean
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemHolder.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemHolder.kt
index 4934b55c4f..44d7e6c63a 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemHolder.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemHolder.kt
@@ -2,12 +2,18 @@ package net.mullvad.mullvadvpn.relaylist
import android.support.v7.widget.RecyclerView.ViewHolder
import android.view.View
+import android.widget.ImageButton
import android.widget.TextView
import net.mullvad.mullvadvpn.R
-class RelayItemHolder(private val view: View) : ViewHolder(view) {
+class RelayItemHolder(
+ private val view: View,
+ private val adapter: RelayListAdapter,
+ var itemPosition: RelayListAdapterPosition
+) : ViewHolder(view) {
private val name: TextView = view.findViewById(R.id.name)
+ private val chevron: ImageButton = view.findViewById(R.id.chevron)
private val countryColor = view.context.getColor(R.color.blue)
private val cityColor = view.context.getColor(R.color.blue40)
@@ -23,6 +29,10 @@ class RelayItemHolder(private val view: View) : ViewHolder(view) {
updateView()
}
+ init {
+ chevron.setOnClickListener { toggle() }
+ }
+
private fun updateView() {
val item = this.item
@@ -34,8 +44,15 @@ class RelayItemHolder(private val view: View) : ViewHolder(view) {
RelayItemType.City -> setViewStyle(cityColor, cityPadding)
RelayItemType.Relay -> setViewStyle(relayColor, relayPadding)
}
+
+ if (item.hasChildren) {
+ chevron.visibility = View.VISIBLE
+ } else {
+ chevron.visibility = View.GONE
+ }
} else {
name.text = ""
+ chevron.visibility = View.GONE
}
}
@@ -50,4 +67,20 @@ class RelayItemHolder(private val view: View) : ViewHolder(view) {
setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
}
}
+
+ private fun toggle() {
+ item?.let { item ->
+ if (!item.expanded) {
+ item.expanded = true
+ chevron.rotation = 180.0F
+ adapter.expandItem(itemPosition, item.visibleChildCount)
+ } else {
+ val childCount = item.visibleChildCount
+
+ item.expanded = false
+ chevron.rotation = 0.0F
+ adapter.collapseItem(itemPosition, childCount)
+ }
+ }
+ }
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt
index 5efa8f5990..e422f47eff 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt
@@ -1,5 +1,8 @@
package net.mullvad.mullvadvpn.relaylist
+import java.lang.ref.WeakReference
+import java.util.LinkedList
+
import android.support.v7.widget.RecyclerView.Adapter
import android.view.LayoutInflater
import android.view.ViewGroup
@@ -8,12 +11,16 @@ import net.mullvad.mullvadvpn.R
class RelayListAdapter : Adapter<RelayItemHolder>() {
private val relayList = fakeRelayList
+ private val activeIndices = LinkedList<WeakReference<RelayListAdapterPosition>>()
override fun onCreateViewHolder(parentView: ViewGroup, type: Int): RelayItemHolder {
val inflater = LayoutInflater.from(parentView.context)
val view = inflater.inflate(R.layout.relay_list_item, parentView, false)
+ val index = RelayListAdapterPosition(0)
+
+ activeIndices.add(WeakReference(index))
- return RelayItemHolder(view)
+ return RelayItemHolder(view, this, index)
}
override fun onBindViewHolder(holder: RelayItemHolder, position: Int) {
@@ -25,6 +32,7 @@ class RelayListAdapter : Adapter<RelayItemHolder>() {
when (itemOrCount) {
is GetItemResult.Item -> {
holder.item = itemOrCount.item
+ holder.itemPosition.position = position
return
}
is GetItemResult.Count -> remaining -= itemOrCount.count
@@ -32,59 +40,91 @@ class RelayListAdapter : Adapter<RelayItemHolder>() {
}
}
- override fun getItemCount(): Int {
- return relayList.map { country -> country.getItemCount() }.sum()
+ override fun getItemCount() = relayList.map { country -> country.visibleItemCount }.sum()
+
+ fun expandItem(itemIndex: RelayListAdapterPosition, childCount: Int) {
+ val position = itemIndex.position
+
+ updateActiveIndices(position, childCount)
+ notifyItemRangeInserted(position + 1, childCount)
+ }
+
+ fun collapseItem(itemIndex: RelayListAdapterPosition, childCount: Int) {
+ val position = itemIndex.position
+
+ updateActiveIndices(position, -childCount)
+ notifyItemRangeRemoved(position + 1, childCount)
+ }
+
+ private fun updateActiveIndices(position: Int, delta: Int) {
+ val activeIndicesIterator = activeIndices.iterator()
+
+ while (activeIndicesIterator.hasNext()) {
+ val index = activeIndicesIterator.next().get()
+
+ if (index == null) {
+ activeIndicesIterator.remove()
+ } else {
+ val indexPosition = index.position
+
+ if (indexPosition > position) {
+ index.position = indexPosition + delta
+ }
+ }
+ }
}
}
val fakeRelayList = listOf(
RelayCountry(
"Australia",
+ false,
listOf(
RelayCity(
"Brisbane",
- listOf(Relay("au-bne-001")),
- false
+ false,
+ listOf(Relay("au-bne-001"))
),
RelayCity(
"Melbourne",
- listOf(Relay("au-mel-002"), Relay("au-mel-003"), Relay("au-mel-004")),
- false
+ false,
+ listOf(Relay("au-mel-002"), Relay("au-mel-003"), Relay("au-mel-004"))
),
RelayCity(
"Perth",
- listOf(Relay("au-per-001")),
- false
+ false,
+ listOf(Relay("au-per-001"))
),
RelayCity(
"Sydney",
+ false,
listOf(
Relay("au1-wireguard"),
Relay("au-syd-001"),
Relay("au-syd-002"),
Relay("au-mel-003")
- ),
- false
+ )
)
- ),
- false
+ )
),
RelayCountry(
"South Africa",
+ false,
listOf(
RelayCity(
"Johannesburg",
- listOf(Relay("za-jnb-001")),
- false
+ false,
+ listOf(Relay("za-jnb-001"))
)
- ),
- false
+ )
),
RelayCountry(
"Sweden",
+ false,
listOf(
RelayCity(
"Gothenburg",
+ false,
listOf(
Relay("se3-wireguard"),
Relay("se5-wireguard"),
@@ -95,11 +135,11 @@ val fakeRelayList = listOf(
Relay("se-got-005"),
Relay("se-got-006"),
Relay("se-got-007")
- ),
- false
+ )
),
RelayCity(
"Helsingborg",
+ false,
listOf(
Relay("se-hel-001"),
Relay("se-hel-002"),
@@ -107,11 +147,11 @@ val fakeRelayList = listOf(
Relay("se-hel-004"),
Relay("se-hel-007"),
Relay("se-hel-008")
- ),
- false
+ )
),
RelayCity(
"Malmö",
+ false,
listOf(
Relay("se4-wireguard"),
Relay("se-mma-001"),
@@ -124,11 +164,11 @@ val fakeRelayList = listOf(
Relay("se-mma-008"),
Relay("se-mma-009"),
Relay("se-mma-010")
- ),
- false
+ )
),
RelayCity(
"Stockholm",
+ false,
listOf(
Relay("se2-wireguard"),
Relay("se6-wireguard"),
@@ -159,10 +199,8 @@ val fakeRelayList = listOf(
Relay("se-sto-023"),
Relay("se-sto-024"),
Relay("se-sto-025")
- ),
- false
+ )
)
- ),
- false
+ )
)
)
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapterPosition.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapterPosition.kt
new file mode 100644
index 0000000000..09dfafebc8
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapterPosition.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.relaylist
+
+data class RelayListAdapterPosition(var position: Int)
diff --git a/android/src/main/res/drawable/icon_chevron_expand.xml b/android/src/main/res/drawable/icon_chevron_expand.xml
new file mode 100644
index 0000000000..f31a8bcf19
--- /dev/null
+++ b/android/src/main/res/drawable/icon_chevron_expand.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rotate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromDegrees="90"
+ android:toDegrees="90"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:drawable="@drawable/icon_chevron"
+ />
diff --git a/android/src/main/res/layout/relay_list_item.xml b/android/src/main/res/layout/relay_list_item.xml
index 16561ca0ec..82b9b4ea41 100644
--- a/android/src/main/res/layout/relay_list_item.xml
+++ b/android/src/main/res/layout/relay_list_item.xml
@@ -26,4 +26,11 @@
android:textStyle="bold"
android:text=""
/>
+ <ImageButton android:id="@+id/chevron"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ android:background="?android:attr/selectableItemBackground"
+ android:src="@drawable/icon_chevron_expand"
+ />
</LinearLayout>