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
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.ComponentName
import android.content.Context
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.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
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.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>() {
override fun onInserted(position: Int, count: Int) { }
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
}
class InterfaceViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
private class InterfaceViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
View.OnClickListener {
init {
itemView.setOnClickListener(this)
@@ -64,38 +65,50 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
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)
fun update(data: Set<String>) {
val oldEmpty = tethered.size() == 0
tethered.clear()
tethered.addAll(data)
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 onCreateViewHolder(parent: ViewGroup, viewType: Int) = InterfaceViewHolder(
ListitemInterfaceBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun onBindViewHolder(holder: InterfaceViewHolder, position: Int) {
holder.binding.data = Data(tethered[position])
override fun getItemCount() = tethered.size() + 1
override fun getItemViewType(position: Int) =
if (position == tethered.size()) VIEW_TYPE_MANAGE else VIEW_TYPE_INTERFACE
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
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 var binder: TetheringService.TetheringBinder? = null
val adapter = InterfaceAdapter()
val adapter = TetheringAdapter()
private val receiver = broadcastReceiver { _, intent ->
adapter.update(ConnectivityManagerHelper.getTetheredIfaces(intent.extras).toSet())
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
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)
val animator = DefaultItemAnimator()
animator.supportsChangeAnimations = false // prevent fading-in/out when rebinding
@@ -128,25 +141,4 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
binder?.fragment = 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:background="?attr/colorPrimary"
android:elevation="4dp"
android:touchscreenBlocksFocus="false"
app:title="@string/app_name"
android:id="@+id/toolbar"/>
<FrameLayout
<android.support.v7.widget.RecyclerView
android:id="@+id/interfaces"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<TextView
android:id="@+id/empty"
android:layout_width="match_parent"
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>
android:layout_weight="1"
android:clipToPadding="false"
android:scrollbars="vertical"
tools:listitem="@layout/listitem_interface"/>
</LinearLayout>
</layout>

View File

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

View File

@@ -12,14 +12,12 @@
android:layout_height="wrap_content"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
android:paddingBottom="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="8dp">
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@{data.icon}"
android:tint="?android:attr/textColorPrimary"
tools:src="@drawable/ic_device_network_wifi"/>
@@ -32,13 +30,14 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:checked="@{data.active}"
android:clickable="false"
android:ellipsize="end"
android:focusable="false"
android:focusableInTouchMode="false"
android:gravity="center_vertical"
android:text="@{data.iface}"
android:checked="@{data.active}"
tools:text="wlan0"/>
</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_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_dns">Downstream DNS server:port</string>
<string name="settings_service_clean">Clean/reapply routing rules</string>