Mimic Android Wi-Fi setting design in tethering
This commit is contained in:
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
9
mobile/src/main/res/drawable/ic_content_add.xml
Normal file
9
mobile/src/main/res/drawable/ic_content_add.xml
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
29
mobile/src/main/res/layout/listitem_manage.xml
Normal file
29
mobile/src/main/res/layout/listitem_manage.xml
Normal 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>
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user