SmartSnackbars

This commit is contained in:
Mygod
2018-08-08 16:03:30 +08:00
parent 7ad1a51832
commit 15d838893c
13 changed files with 105 additions and 47 deletions

View File

@@ -9,8 +9,6 @@ import android.net.wifi.WifiManager
import android.os.Build
import android.os.Handler
import android.preference.PreferenceManager
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.core.content.getSystemService
import be.mygod.vpnhotspot.util.DeviceStorageApp
import be.mygod.vpnhotspot.util.Event0
@@ -55,6 +53,4 @@ class App : Application() {
val masquerade: Boolean get() = pref.getBoolean(KEY_MASQUERADE, true)
val cleanRoutings = Event0()
fun toast(@StringRes resId: Int) = handler.post { Toast.makeText(this, resId, Toast.LENGTH_SHORT).show() }
}

View File

@@ -3,7 +3,6 @@ package be.mygod.vpnhotspot
import android.content.Intent
import android.content.IntentFilter
import android.net.wifi.WifiManager
import android.widget.Toast
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.manage.LocalOnlyHotspotManager
@@ -11,6 +10,7 @@ import be.mygod.vpnhotspot.net.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.util.debugLog
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.crashlytics.android.Crashlytics
@RequiresApi(26)
@@ -89,7 +89,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
getString(R.string.tethering_temp_hotspot_failure_tethering_disallowed)
else -> getString(R.string.failure_reason_unknown, reason)
})
Toast.makeText(this@LocalOnlyHotspotService, message, Toast.LENGTH_SHORT).show()
SmartSnackbar.make(message).show()
startFailure(if (reason == WifiManager.LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE) null else
StartFailure(message))
}
@@ -98,7 +98,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
e.printStackTrace()
startFailure(e)
} catch (e: SecurityException) {
Toast.makeText(this, e.message, Toast.LENGTH_LONG).show()
SmartSnackbar.make(e.localizedMessage).show()
e.printStackTrace()
startFailure(e)
}

View File

@@ -1,9 +1,9 @@
package be.mygod.vpnhotspot
import android.widget.Toast
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.net.UpstreamMonitor
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.crashlytics.android.Crashlytics
import java.net.InetAddress
import java.net.InterfaceAddress
@@ -48,9 +48,9 @@ class LocalOnlyInterfaceManager(val downstream: String) : UpstreamMonitor.Callba
routing.ipForward() // local only interfaces need not enable ip_forward
.rule().forward(strict)
if (app.masquerade) routing.masquerade(strict)
if (!routing.dnsRedirect(dns).start()) app.toast(R.string.noisy_su_failure)
if (!routing.dnsRedirect(dns).start()) SmartSnackbar.make(R.string.noisy_su_failure).show()
} catch (e: SocketException) {
Toast.makeText(app, e.message, Toast.LENGTH_SHORT).show()
SmartSnackbar.make(e.localizedMessage).show()
Crashlytics.logException(e)
routing = null
}

View File

@@ -18,12 +18,17 @@ import be.mygod.vpnhotspot.client.ClientsFragment
import be.mygod.vpnhotspot.databinding.ActivityMainBinding
import be.mygod.vpnhotspot.manage.TetheringFragment
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.snackbar.Snackbar
import q.rorbin.badgeview.QBadgeView
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener, ServiceConnection {
companion object {
var current: MainActivity? = null
}
private lateinit var binding: ActivityMainBinding
private lateinit var badge: QBadgeView
private var clients: ClientMonitorService.Binder? = null
@@ -33,7 +38,6 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
.build()
}
fun launchUrl(url: Uri) = customTabsIntent.launchUrl(this, url)
fun snackbar(text: CharSequence = "") = Snackbar.make(binding.fragmentHolder, text, Snackbar.LENGTH_LONG)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -47,6 +51,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
badge.badgeGravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
badge.setGravityOffset(16f, 0f, true)
ServiceForegroundConnector(this, this, ClientMonitorService::class)
SmartSnackbar.Register(binding.fragmentHolder)
}
override fun onNavigationItemSelected(item: MenuItem) = when (item.itemId) {

View File

@@ -11,7 +11,6 @@ import android.net.wifi.p2p.WifiP2pInfo
import android.net.wifi.p2p.WifiP2pManager
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.core.content.getSystemService
import be.mygod.vpnhotspot.App.Companion.app
@@ -22,6 +21,7 @@ import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupI
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps
import be.mygod.vpnhotspot.util.*
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.crashlytics.android.Crashlytics
import java.lang.reflect.InvocationTargetException
@@ -50,11 +50,11 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
WpsInfo.KEYPAD
}
}, object : WifiP2pManager.ActionListener {
override fun onSuccess() = Toast.makeText(this@RepeaterService,
if (pin == null) R.string.repeater_wps_success_pbc else R.string.repeater_wps_success_keypad,
Toast.LENGTH_SHORT).show()
override fun onFailure(reason: Int) = Toast.makeText(this@RepeaterService,
formatReason(R.string.repeater_wps_failure, reason), Toast.LENGTH_SHORT).show()
override fun onSuccess() = SmartSnackbar.make(
if (pin == null) R.string.repeater_wps_success_pbc else R.string.repeater_wps_success_keypad)
.shortToast().show()
override fun onFailure(reason: Int) = SmartSnackbar.make(
formatReason(R.string.repeater_wps_failure, reason)).show()
})
}
@@ -64,10 +64,10 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
fun resetCredentials() = (groups + group).filterNotNull().forEach {
p2pManager.deletePersistentGroup(channel, it.netId, object : WifiP2pManager.ActionListener {
override fun onSuccess() = Toast.makeText(this@RepeaterService,
R.string.repeater_reset_credentials_success, Toast.LENGTH_SHORT).show()
override fun onFailure(reason: Int) = Toast.makeText(this@RepeaterService,
formatReason(R.string.repeater_reset_credentials_failure, reason), Toast.LENGTH_SHORT).show()
override fun onSuccess() = SmartSnackbar.make(R.string.repeater_reset_credentials_success)
.shortToast().show()
override fun onFailure(reason: Int) = SmartSnackbar.make(
formatReason(R.string.repeater_reset_credentials_failure, reason)).show()
})
}
@@ -81,7 +81,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
} catch (e: ReflectiveOperationException) {
e.printStackTrace()
Crashlytics.logException(e)
Toast.makeText(this@RepeaterService, e.message, Toast.LENGTH_LONG).show()
SmartSnackbar.make(e.localizedMessage).show()
}
}
}
@@ -115,7 +115,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
binder.statusChanged()
}
private fun formatReason(@StringRes resId: Int, reason: Int): String? {
private fun formatReason(@StringRes resId: Int, reason: Int): String {
val result = getString(resId, when (reason) {
WifiP2pManager.ERROR -> getString(R.string.repeater_failure_reason_error)
WifiP2pManager.P2P_UNSUPPORTED -> getString(R.string.repeater_failure_reason_p2p_unsupported)
@@ -147,14 +147,13 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
p2pManager.setWifiP2pChannels(channel, 0, oc, object : WifiP2pManager.ActionListener {
override fun onSuccess() { }
override fun onFailure(reason: Int) {
Toast.makeText(this@RepeaterService, formatReason(R.string.repeater_set_oc_failure, reason),
Toast.LENGTH_SHORT).show()
SmartSnackbar.make(formatReason(R.string.repeater_set_oc_failure, reason)).show()
}
})
} catch (e: InvocationTargetException) {
if (oc != 0) {
val message = getString(R.string.repeater_set_oc_failure, e.message)
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
SmartSnackbar.make(message).show()
Crashlytics.logException(Failure(message))
}
e.printStackTrace()
@@ -195,9 +194,8 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() = doStart()
override fun onFailure(reason: Int) {
Toast.makeText(this@RepeaterService,
formatReason(R.string.repeater_remove_old_group_failure, reason),
Toast.LENGTH_SHORT).show()
SmartSnackbar.make(formatReason(R.string.repeater_remove_old_group_failure, reason))
.show()
}
})
}
@@ -237,8 +235,8 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
status = Status.ACTIVE
showNotification(group)
}
private fun startFailure(msg: CharSequence?, group: WifiP2pGroup? = null) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
private fun startFailure(msg: CharSequence, group: WifiP2pGroup? = null) {
SmartSnackbar.make(msg).show()
showNotification()
if (group != null) removeGroup() else clean()
}
@@ -251,8 +249,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
override fun onSuccess() = clean()
override fun onFailure(reason: Int) {
if (reason == WifiP2pManager.BUSY) clean() else { // assuming it's already gone
Toast.makeText(this@RepeaterService,
formatReason(R.string.repeater_remove_group_failure, reason), Toast.LENGTH_SHORT).show()
SmartSnackbar.make(formatReason(R.string.repeater_remove_group_failure, reason)).show()
status = Status.ACTIVE
}
}

View File

@@ -15,6 +15,7 @@ import be.mygod.vpnhotspot.net.UpstreamMonitor
import be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat
import be.mygod.vpnhotspot.preference.SharedPreferenceDataStore
import be.mygod.vpnhotspot.util.loggerSuStream
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.crashlytics.android.Crashlytics
import com.takisoft.preferencex.PreferenceFragmentCompat
import java.io.File
@@ -27,7 +28,6 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = SharedPreferenceDataStore(app.pref)
addPreferencesFromResource(R.xml.pref_settings)
val mainActivity = activity as MainActivity
val boot = findPreference("service.repeater.startOnBoot") as SwitchPreference
boot.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
@@ -35,7 +35,7 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
}
boot.isChecked = BootReceiver.enabled
findPreference("service.clean").setOnPreferenceClickListener {
if (Routing.clean() == null) mainActivity.snackbar().setText(R.string.root_unavailable).show()
if (Routing.clean() == null) SmartSnackbar.make(R.string.root_unavailable).show()
else app.cleanRoutings()
true
}
@@ -96,7 +96,7 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
true
}
findPreference("misc.source").setOnPreferenceClickListener {
mainActivity.launchUrl("https://github.com/Mygod/VPNHotspot".toUri())
(activity as MainActivity).launchUrl("https://github.com/Mygod/VPNHotspot".toUri())
true
}
findPreference("misc.donate").setOnPreferenceClickListener {

View File

@@ -9,6 +9,7 @@ import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.UpstreamMonitor
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.crashlytics.android.Crashlytics
import java.net.InetAddress
import java.net.SocketException
@@ -32,7 +33,7 @@ class TetheringService : IpNeighbourMonitoringService(), UpstreamMonitor.Callbac
private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent ->
synchronized(routings) {
for (iface in routings.keys - TetheringManager.getTetheredIfaces(intent.extras))
for (iface in routings.keys - TetheringManager.getTetheredIfaces(intent.extras!!))
routings.remove(iface)?.stop()
updateRoutingsLocked()
}
@@ -60,7 +61,7 @@ class TetheringService : IpNeighbourMonitoringService(), UpstreamMonitor.Callbac
routings.remove(downstream)
failed = true
}
if (failed) app.toast(R.string.noisy_su_failure)
if (failed) SmartSnackbar.make(R.string.noisy_su_failure).show()
} else if (!receiverRegistered) {
registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
app.cleanRoutings[this] = {

View File

@@ -22,6 +22,7 @@ import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration
import be.mygod.vpnhotspot.net.wifi.WifiP2pDialogFragment
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.util.formatAddresses
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.crashlytics.android.Crashlytics
import java.net.NetworkInterface
import java.net.SocketException
@@ -100,14 +101,13 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
val binder = binder
val group = binder?.service?.group
val ssid = group?.networkName
val mainActivity = parent.activity as MainActivity
if (ssid != null) {
val wifi = WifiConfiguration()
val conf = P2pSupplicantConfiguration()
wifi.SSID = ssid
wifi.preSharedKey = group.passphrase
if (wifi.preSharedKey == null) {
wifi.preSharedKey = conf.readPsk { mainActivity.snackbar(it.message.toString()).show() }
wifi.preSharedKey = conf.readPsk { SmartSnackbar.make(it.message.toString()).show() }
}
if (wifi.preSharedKey != null) {
WifiP2pDialogFragment().apply {
@@ -118,7 +118,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
return
}
}
mainActivity.snackbar().setText(R.string.repeater_configure_failure).show()
SmartSnackbar.make(R.string.repeater_configure_failure).show()
}
}

View File

@@ -23,6 +23,7 @@ import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.wifi.WifiApManager
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.crashlytics.android.Crashlytics
import java.lang.reflect.InvocationTargetException
@@ -92,7 +93,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override fun onTetheringStarted() = data.notifyChange()
override fun onTetheringFailed() =
(parent.activity as MainActivity).snackbar().setText(R.string.tethering_manage_failed).show()
SmartSnackbar.make(R.string.tethering_manage_failed).show()
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
(viewHolder as ViewHolder).manager = this

View File

@@ -4,6 +4,7 @@ import android.util.Log
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.util.thread
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.crashlytics.android.Crashlytics
import java.io.IOException
import java.io.InterruptedIOException
@@ -67,7 +68,7 @@ abstract class IpMonitor : Runnable {
if (err.isNotBlank()) {
Crashlytics.log(Log.ERROR, javaClass.simpleName, err)
Crashlytics.logException(FlushFailure())
app.toast(R.string.noisy_su_failure)
SmartSnackbar.make(R.string.noisy_su_failure).show()
}
}
process.inputStream.bufferedReader().useLines(this::processLines)

View File

@@ -12,9 +12,9 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.MainActivity
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.manage.TetheringFragment
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.google.android.material.textfield.TextInputLayout
import java.nio.charset.Charset
@@ -90,8 +90,8 @@ class WifiP2pDialogFragment : DialogFragment(), TextWatcher, DialogInterface.OnC
app.handler.postDelayed((targetFragment as TetheringFragment).adapter.repeaterManager
.binder!!::requestGroupUpdate, 1000)
}
false -> (activity as MainActivity).snackbar().setText(R.string.noisy_su_failure).show()
null -> (activity as MainActivity).snackbar().setText(R.string.root_unavailable).show()
false -> SmartSnackbar.make(R.string.noisy_su_failure).show()
null -> SmartSnackbar.make(R.string.root_unavailable).show()
}
DialogInterface.BUTTON_NEUTRAL ->
(targetFragment as TetheringFragment).adapter.repeaterManager.binder!!.resetCredentials()

View File

@@ -10,6 +10,7 @@ import androidx.databinding.BindingAdapter
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.BuildConfig
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.crashlytics.android.Crashlytics
import java.net.NetworkInterface
import java.net.SocketException
@@ -57,7 +58,7 @@ fun thread(name: String? = null, start: Boolean = true, isDaemon: Boolean = fals
contextClassLoader: ClassLoader? = null, priority: Int = -1, block: () -> Unit): Thread {
val thread = kotlin.concurrent.thread(false, isDaemon, contextClassLoader, name, priority, block)
thread.setUncaughtExceptionHandler { _, e ->
app.toast(R.string.noisy_su_failure)
SmartSnackbar.make(R.string.noisy_su_failure).show()
Crashlytics.logException(e)
}
if (start) thread.start()

View File

@@ -0,0 +1,56 @@
package be.mygod.vpnhotspot.widget
import android.annotation.SuppressLint
import android.view.View
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import be.mygod.vpnhotspot.App.Companion.app
import com.google.android.material.snackbar.Snackbar
sealed class SmartSnackbar {
companion object {
@SuppressLint("StaticFieldLeak")
private var holder: View? = null
fun make(@StringRes text: Int): SmartSnackbar = make(app.getText(text))
fun make(text: CharSequence = ""): SmartSnackbar {
val holder = holder
return if (holder == null) ToastWrapper(Toast.makeText(app, text, Toast.LENGTH_LONG)) else
SnackbarWrapper(Snackbar.make(holder, text, Snackbar.LENGTH_LONG))
}
}
class Register(private val view: View) : LifecycleObserver {
init {
(view.context as LifecycleOwner).lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
holder = view
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
if (holder === view) holder = null
}
}
abstract fun show()
open fun shortToast() = this
}
private class SnackbarWrapper(private val snackbar: Snackbar) : SmartSnackbar() {
override fun show() = snackbar.show()
}
private class ToastWrapper(private val toast: Toast) : SmartSnackbar() {
override fun show() {
app.handler.post(toast::show)
}
override fun shortToast() = apply { toast.duration = Toast.LENGTH_SHORT }
}