Refine UI for tethering
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
package be.mygod.vpnhotspot
|
package be.mygod.vpnhotspot
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.databinding.BaseObservable
|
import android.databinding.BaseObservable
|
||||||
@@ -11,6 +13,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.text.Html
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -18,6 +21,7 @@ import be.mygod.vpnhotspot.App.Companion.app
|
|||||||
import be.mygod.vpnhotspot.NetUtils.tetheredIfaces
|
import be.mygod.vpnhotspot.NetUtils.tetheredIfaces
|
||||||
import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding
|
import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding
|
||||||
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
||||||
|
import be.mygod.vpnhotspot.widget.TextViewLinkHandler
|
||||||
|
|
||||||
class TetheringFragment : Fragment() {
|
class TetheringFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -83,9 +87,13 @@ class TetheringFragment : Fragment() {
|
|||||||
private val tethered = SortedList(String::class.java, StringSorter)
|
private val tethered = SortedList(String::class.java, StringSorter)
|
||||||
|
|
||||||
fun update(data: Set<String> = VpnListener.connectivityManager.tetheredIfaces.toSet()) {
|
fun update(data: Set<String> = VpnListener.connectivityManager.tetheredIfaces.toSet()) {
|
||||||
|
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()
|
||||||
@@ -96,6 +104,7 @@ class TetheringFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentTetheringBinding
|
||||||
private val adapter = InterfaceAdapter()
|
private val adapter = InterfaceAdapter()
|
||||||
private val receiver = broadcastReceiver { _, intent ->
|
private val receiver = broadcastReceiver { _, intent ->
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
@@ -107,8 +116,12 @@ class TetheringFragment : Fragment() {
|
|||||||
private var receiverRegistered = false
|
private var receiverRegistered = false
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
val binding = DataBindingUtil.inflate<FragmentTetheringBinding>(inflater, R.layout.fragment_tethering,
|
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_tethering, container, false)
|
||||||
container, false)
|
binding.empty.text = Html.fromHtml(getString(R.string.tethering_no_interfaces))
|
||||||
|
binding.empty.movementMethod = TextViewLinkHandler.create {
|
||||||
|
startActivity(Intent().setClassName("com.android.settings",
|
||||||
|
"com.android.settings.Settings\$TetherSettingsActivity"))
|
||||||
|
}
|
||||||
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
|
||||||
@@ -138,4 +151,16 @@ class TetheringFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package be.mygod.vpnhotspot.widget
|
||||||
|
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.text.style.URLSpan
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.widget.TextView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on: https://stackoverflow.com/a/32443884/2245107
|
||||||
|
*/
|
||||||
|
abstract class TextViewLinkHandler : LinkMovementMethod() {
|
||||||
|
companion object {
|
||||||
|
fun create(handler: (String) -> Unit) = object : TextViewLinkHandler() {
|
||||||
|
override fun onLinkClick(url: String) = handler(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
|
||||||
|
if (event.action != MotionEvent.ACTION_UP) return super.onTouchEvent(widget, buffer, event)
|
||||||
|
val x = event.x - widget.totalPaddingLeft + widget.scrollX
|
||||||
|
val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY
|
||||||
|
val layout = widget.layout
|
||||||
|
val line = layout.getLineForVertical(y)
|
||||||
|
val off = layout.getOffsetForHorizontal(line, x)
|
||||||
|
val link = buffer.getSpans(off, off, URLSpan::class.java)
|
||||||
|
if (link.isNotEmpty()) onLinkClick(link[0].url)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun onLinkClick(url: String)
|
||||||
|
}
|
||||||
@@ -16,13 +16,26 @@
|
|||||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||||
android:id="@+id/toolbar"/>
|
android:id="@+id/toolbar"/>
|
||||||
<android.support.v7.widget.RecyclerView
|
<FrameLayout
|
||||||
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">
|
||||||
android:clipToPadding="false"
|
<TextView
|
||||||
android:scrollbars="vertical"
|
android:id="@+id/empty"
|
||||||
tools:listitem="@layout/listitem_interface"/>
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:linksClickable="true"
|
||||||
|
android:padding="16dp"
|
||||||
|
tools: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>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:src="@{data.icon}"
|
android:src="@{data.icon}"
|
||||||
|
android:tint="?android:attr/textColorPrimary"
|
||||||
tools:src="@drawable/ic_device_network_wifi"/>
|
tools:src="@drawable/ic_device_network_wifi"/>
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
|
|||||||
@@ -6,4 +6,6 @@
|
|||||||
<item quantity="other">%d connected devices</item>
|
<item quantity="other">%d connected devices</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="exception_interface_not_found">Fatal: Downstream interface not found</string>
|
<string name="exception_interface_not_found">Fatal: Downstream interface not found</string>
|
||||||
|
<string name="tethering_no_interfaces"><![CDATA[To use this feature, turn on any <a href="#">system
|
||||||
|
tethering</a> first.]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user