diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-03-16 20:07:02 +0000 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-03-18 17:03:54 +0000 |
| commit | 88212cb101afbd45ad8e78d53781c620e2a5e463 (patch) | |
| tree | 94732b76f42d8d07d85aa4bf5d3cf3e4777b4dd6 /android/src | |
| parent | cdc6e80d840e11cc75d3daa4929b2d4dc1c42e8b (diff) | |
| download | mullvadvpn-88212cb101afbd45ad8e78d53781c620e2a5e463.tar.xz mullvadvpn-88212cb101afbd45ad8e78d53781c620e2a5e463.zip | |
Implement relay list item expansion and collapse
Diffstat (limited to 'android/src')
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> |
