Mimic Android Wi-Fi setting design in tethering

This commit is contained in:
Mygod
2018-01-21 13:16:21 -08:00
parent 5ad840fe43
commit de7c6c5d8c
8 changed files with 82 additions and 79 deletions

View File

@@ -1,7 +1,5 @@
package be.mygod.vpnhotspot package be.mygod.vpnhotspot
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -16,9 +14,7 @@ import android.support.v7.util.SortedList
import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding
@@ -26,7 +22,12 @@ import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
import be.mygod.vpnhotspot.net.ConnectivityManagerHelper import be.mygod.vpnhotspot.net.ConnectivityManagerHelper
import be.mygod.vpnhotspot.net.TetherType import be.mygod.vpnhotspot.net.TetherType
class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener { class TetheringFragment : Fragment(), ServiceConnection {
companion object {
private const val VIEW_TYPE_INTERFACE = 0
private const val VIEW_TYPE_MANAGE = 1
}
private abstract class BaseSorter<T> : SortedList.Callback<T>() { private abstract class BaseSorter<T> : SortedList.Callback<T>() {
override fun onInserted(position: Int, count: Int) { } override fun onInserted(position: Int, count: Int) { }
override fun areContentsTheSame(oldItem: T?, newItem: T?): Boolean = oldItem == newItem override fun areContentsTheSame(oldItem: T?, newItem: T?): Boolean = oldItem == newItem
@@ -48,7 +49,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
var active = binder?.active?.contains(iface) == true var active = binder?.active?.contains(iface) == true
} }
class InterfaceViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root), private class InterfaceViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
View.OnClickListener { View.OnClickListener {
init { init {
itemView.setOnClickListener(this) itemView.setOnClickListener(this)
@@ -64,38 +65,50 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
data.active = !data.active data.active = !data.active
} }
} }
inner class InterfaceAdapter : RecyclerView.Adapter<InterfaceViewHolder>() { private class ManageViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
init {
view.setOnClickListener(this)
}
override fun onClick(v: View?) = itemView.context.startActivity(Intent()
.setClassName("com.android.settings", "com.android.settings.Settings\$TetherSettingsActivity"))
}
inner class TetheringAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val tethered = SortedList(String::class.java, StringSorter) private val tethered = SortedList(String::class.java, StringSorter)
fun update(data: Set<String>) { fun update(data: Set<String>) {
val oldEmpty = tethered.size() == 0
tethered.clear() tethered.clear()
tethered.addAll(data) tethered.addAll(data)
notifyDataSetChanged() notifyDataSetChanged()
if (oldEmpty != data.isEmpty())
if (oldEmpty) crossFade(binding.empty, binding.interfaces)
else crossFade(binding.interfaces, binding.empty)
} }
override fun getItemCount() = tethered.size() override fun getItemCount() = tethered.size() + 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = InterfaceViewHolder( override fun getItemViewType(position: Int) =
ListitemInterfaceBinding.inflate(LayoutInflater.from(parent.context), parent, false)) if (position == tethered.size()) VIEW_TYPE_MANAGE else VIEW_TYPE_INTERFACE
override fun onBindViewHolder(holder: InterfaceViewHolder, position: Int) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
holder.binding.data = Data(tethered[position]) val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
VIEW_TYPE_INTERFACE -> InterfaceViewHolder(ListitemInterfaceBinding.inflate(inflater, parent, false))
VIEW_TYPE_MANAGE -> ManageViewHolder(inflater.inflate(R.layout.listitem_manage, parent, false))
else -> throw IllegalArgumentException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is InterfaceViewHolder -> holder.binding.data = Data(tethered[position])
}
} }
} }
private lateinit var binding: FragmentTetheringBinding private lateinit var binding: FragmentTetheringBinding
private var binder: TetheringService.TetheringBinder? = null private var binder: TetheringService.TetheringBinder? = null
val adapter = InterfaceAdapter() val adapter = TetheringAdapter()
private val receiver = broadcastReceiver { _, intent -> private val receiver = broadcastReceiver { _, intent ->
adapter.update(ConnectivityManagerHelper.getTetheredIfaces(intent.extras).toSet()) adapter.update(ConnectivityManagerHelper.getTetheredIfaces(intent.extras).toSet())
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_tethering, container, false) binding = DataBindingUtil.inflate(inflater, R.layout.fragment_tethering, container, false)
binding.toolbar.inflateMenu(R.menu.tethering)
binding.toolbar.setOnMenuItemClickListener(this)
binding.interfaces.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) binding.interfaces.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
val animator = DefaultItemAnimator() val animator = DefaultItemAnimator()
animator.supportsChangeAnimations = false // prevent fading-in/out when rebinding animator.supportsChangeAnimations = false // prevent fading-in/out when rebinding
@@ -128,25 +141,4 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
binder?.fragment = null binder?.fragment = null
binder = null binder = null
} }
override fun onMenuItemClick(item: MenuItem) = when (item.itemId) {
R.id.systemTethering -> {
startActivity(Intent().setClassName("com.android.settings",
"com.android.settings.Settings\$TetherSettingsActivity"))
true
}
else -> false
}
private fun crossFade(old: View, new: View) {
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
old.animate().alpha(0F).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
old.visibility = View.GONE
}
}).duration = shortAnimTime
new.alpha = 0F
new.visibility = View.VISIBLE
new.animate().alpha(1F).setListener(null).duration = shortAnimTime
}
} }

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@@ -12,28 +12,15 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:elevation="4dp" android:elevation="4dp"
android:touchscreenBlocksFocus="false"
app:title="@string/app_name" app:title="@string/app_name"
android:id="@+id/toolbar"/> android:id="@+id/toolbar"/>
<FrameLayout <android.support.v7.widget.RecyclerView
android:id="@+id/interfaces"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"> android:layout_weight="1"
<TextView android:clipToPadding="false"
android:id="@+id/empty" android:scrollbars="vertical"
android:layout_width="match_parent" tools:listitem="@layout/listitem_interface"/>
android:layout_height="match_parent"
android:padding="16dp"
android:text="@string/tethering_no_interfaces"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/interfaces"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbars="vertical"
android:visibility="gone"
tools:listitem="@layout/listitem_interface"
tools:visibility="visible"/>
</FrameLayout>
</LinearLayout> </LinearLayout>
</layout> </layout>

View File

@@ -11,10 +11,10 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="4dp" android:paddingBottom="8dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="4dp"> android:paddingTop="8dp">
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -12,14 +12,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:focusable="true" android:focusable="true"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:paddingBottom="8dp" android:padding="16dp">
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="8dp">
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@{data.icon}" android:src="@{data.icon}"
android:tint="?android:attr/textColorPrimary" android:tint="?android:attr/textColorPrimary"
tools:src="@drawable/ic_device_network_wifi"/> tools:src="@drawable/ic_device_network_wifi"/>
@@ -32,13 +30,14 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_gravity="center_vertical"
android:checked="@{data.active}"
android:clickable="false" android:clickable="false"
android:ellipsize="end" android:ellipsize="end"
android:focusable="false" android:focusable="false"
android:focusableInTouchMode="false" android:focusableInTouchMode="false"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="@{data.iface}" android:text="@{data.iface}"
android:checked="@{data.active}"
tools:text="wlan0"/> tools:text="wlan0"/>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_content_add"
android:tint="@color/colorAccent"/>
<Space
android:layout_width="16dp"
android:layout_height="0dp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:text="Manage..."/>
</LinearLayout>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/systemTethering"
android:icon="@drawable/ic_action_settings"
android:title="@string/tethering_system_tethering"
app:showAsAction="ifRoom"/>
</menu>

View File

@@ -37,9 +37,6 @@
<string name="repeater_failure_reason_no_service_requests">no service requests added</string> <string name="repeater_failure_reason_no_service_requests">no service requests added</string>
<string name="repeater_failure_reason_unknown">unknown #%d</string> <string name="repeater_failure_reason_unknown">unknown #%d</string>
<string name="tethering_system_tethering">System tethering</string>
<string name="tethering_no_interfaces">To use this feature, turn on any system tethering first.</string>
<string name="settings_service">Service</string> <string name="settings_service">Service</string>
<string name="settings_service_dns">Downstream DNS server:port</string> <string name="settings_service_dns">Downstream DNS server:port</string>
<string name="settings_service_clean">Clean/reapply routing rules</string> <string name="settings_service_clean">Clean/reapply routing rules</string>