Optimized UI refresh to save battery. Extracted all strings to xml to enable localizations.

This commit is contained in:
Revertron 2022-11-07 17:14:51 +01:00
parent 8615d43761
commit a0fd3854bc
8 changed files with 142 additions and 119 deletions

View file

@ -108,7 +108,7 @@ class DnsActivity : AppCompatActivity() {
view.findViewById<ImageButton>(R.id.deletePeerButton).setOnClickListener { button ->
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setTitle("Remove ${server}?")
builder.setTitle(getString(R.string.dns_remove_title, server))
builder.setPositiveButton(getString(R.string.remove)) { dialog, _ ->
servers.removeAt(button.tag as Int)
preferences.edit().apply {

View file

@ -1,25 +1,27 @@
package eu.neilalexander.yggdrasil
import android.app.Application
import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
class GlobalApplication: Application() {
private var state = PacketTunnelState
private lateinit var config: ConfigurationProxy
var updaterConnections: Int = 0
override fun onCreate() {
super.onCreate()
config = ConfigurationProxy(applicationContext)
LocalBroadcastManager.getInstance(this).registerReceiver(
state, IntentFilter(PacketTunnelProvider.RECEIVER_INTENT)
)
}
override fun onTerminate() {
super.onTerminate()
fun subscribe() {
updaterConnections++
}
LocalBroadcastManager.getInstance(this).unregisterReceiver(state)
fun unsubscribe() {
if (updaterConnections > 0) {
updaterConnections--
}
}
fun needUiUpdates(): Boolean {
return updaterConnections > 0
}
}

View file

@ -12,12 +12,12 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT
import mobile.Mobile
import org.json.JSONArray
class MainActivity : AppCompatActivity() {
private var state = PacketTunnelState
private lateinit var enabledSwitch: Switch
private lateinit var enabledLabel: TextView
private lateinit var ipAddressLabel: TextView
@ -116,20 +116,27 @@ class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
LocalBroadcastManager.getInstance(this).registerReceiver(
receiver, IntentFilter(PacketTunnelState.RECEIVER_INTENT)
receiver, IntentFilter(STATE_INTENT)
)
val preferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
if (serverString!!.isNotEmpty()) {
val servers = serverString.split(",")
dnsLabel.text = when (servers.size) {
0 -> "No servers"
1 -> "1 server"
else -> "${servers.size} servers"
0 -> getString(R.string.dns_no_servers)
1 -> getString(R.string.dns_one_server)
else -> getString(R.string.dns_many_servers, servers.size)
}
} else {
dnsLabel.text = "No servers"
dnsLabel.text = getString(R.string.dns_no_servers)
}
(application as GlobalApplication).subscribe()
}
override fun onPause() {
super.onPause()
(application as GlobalApplication).unsubscribe()
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
@ -138,33 +145,39 @@ class MainActivity : AppCompatActivity() {
"state" -> {
enabledLabel.text = if (intent.getBooleanExtra("started", false)) {
enabledSwitch.isChecked = true
if (state.dhtCount() == 0) {
var count = 0
if (intent.hasExtra("dht")) {
val dht = intent.getStringExtra("dht")
if (dht != null && dht != "null") {
val dhtState = JSONArray(dht)
count = dhtState.length()
}
}
if (count == 0) {
enabledLabel.setTextColor(Color.RED)
"No connectivity"
getString(R.string.main_no_connectivity)
} else {
enabledLabel.setTextColor(Color.GREEN)
"Enabled"
getString(R.string.main_enabled)
}
} else {
enabledSwitch.isChecked = false
enabledLabel.setTextColor(Color.GRAY)
"Not enabled"
getString(R.string.main_disabled)
}
ipAddressLabel.text = intent.getStringExtra("ip") ?: "N/A"
subnetLabel.text = intent.getStringExtra("subnet") ?: "N/A"
coordinatesLabel.text = intent.getStringExtra("coords") ?: "[]"
peersLabel.text = when (val count = state.peerCount()) {
0 -> "No peers"
1 -> "1 peer"
else -> "$count peers"
if (intent.hasExtra("peers")) {
val peerState = JSONArray(intent.getStringExtra("peers") ?: "[]")
peersLabel.text = when (val count = peerState.length()) {
0 -> getString(R.string.main_no_peers)
1 -> getString(R.string.main_one_peer)
else -> getString(R.string.main_many_peers, count)
}
}
}
}
}
override fun onPause() {
super.onPause()
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}
}

View file

@ -17,7 +17,7 @@ private const val TAG = "PacketTunnelProvider"
class PacketTunnelProvider: VpnService() {
companion object {
const val RECEIVER_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.MESSAGE"
const val STATE_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.STATE_MESSAGE"
const val ACTION_START = "eu.neilalexander.yggdrasil.PacketTunnelProvider.START"
const val ACTION_STOP = "eu.neilalexander.yggdrasil.PacketTunnelProvider.STOP"
@ -130,7 +130,7 @@ class PacketTunnelProvider: VpnService() {
updater()
}
val intent = Intent(RECEIVER_INTENT)
val intent = Intent(STATE_INTENT)
intent.putExtra("type", "state")
intent.putExtra("started", true)
intent.putExtra("ip", yggdrasil.addressString)
@ -173,7 +173,7 @@ class PacketTunnelProvider: VpnService() {
updateThread = null
}
val intent = Intent(RECEIVER_INTENT)
val intent = Intent(STATE_INTENT)
intent.putExtra("type", "state")
intent.putExtra("started", false)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
@ -183,7 +183,9 @@ class PacketTunnelProvider: VpnService() {
private fun updater() {
updates@ while (started.get()) {
val intent = Intent(RECEIVER_INTENT)
if ((application as GlobalApplication).needUiUpdates()) {
Log.d(TAG, "Sending stats to UI...")
val intent = Intent(STATE_INTENT)
intent.putExtra("type", "state")
intent.putExtra("started", true)
intent.putExtra("ip", yggdrasil.addressString)
@ -192,6 +194,13 @@ class PacketTunnelProvider: VpnService() {
intent.putExtra("peers", yggdrasil.peersJSON)
intent.putExtra("dht", yggdrasil.dhtjson)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
} else {
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
return
}
}
if (Thread.currentThread().isInterrupted) {
break@updates
}

View file

@ -1,53 +0,0 @@
package eu.neilalexander.yggdrasil
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.json.JSONArray
object PacketTunnelState: BroadcastReceiver() {
var dhtState: JSONArray? = null
private set
var peersState: JSONArray? = null
private set
const val RECEIVER_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelState.MESSAGE"
fun peerCount(): Int {
if (peersState == null) {
return 0
}
return peersState!!.length()
}
fun dhtCount(): Int {
if (dhtState == null) {
return 0
}
return dhtState!!.length()
}
override fun onReceive(context: Context?, intent: Intent) {
when (intent.getStringExtra("type")) {
"state" -> {
var dht = intent.getStringExtra("dht")
var peers = intent.getStringExtra("peers")
if (dht == null || dht == "null") {
dht = "[]"
}
if (peers == null || peers == "null") {
peers = "[]"
}
peersState = JSONArray(peers)
dhtState = JSONArray(dht)
intent.action = RECEIVER_INTENT
LocalBroadcastManager.getInstance(context!!).sendBroadcast(intent)
}
}
}
}

View file

@ -1,19 +1,25 @@
package eu.neilalexander.yggdrasil
import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.android.material.textfield.TextInputEditText
import org.json.JSONArray
import org.json.JSONObject
class PeersActivity : AppCompatActivity() {
private var state = PacketTunnelState
private lateinit var config: ConfigurationProxy
private lateinit var inflater: LayoutInflater
private lateinit var peers: Array<JSONObject>
private lateinit var connectedTableLayout: TableLayout
private lateinit var connectedTableLabel: TextView
@ -29,6 +35,7 @@ class PeersActivity : AppCompatActivity() {
config = ConfigurationProxy(applicationContext)
inflater = LayoutInflater.from(this)
peers = emptyArray()
connectedTableLayout = findViewById(R.id.connectedPeersTableLayout)
connectedTableLabel = findViewById(R.id.connectedPeersLabel)
@ -52,16 +59,16 @@ class PeersActivity : AppCompatActivity() {
val view = inflater.inflate(R.layout.dialog_addpeer, null)
val input = view.findViewById<TextInputEditText>(R.id.addPeerInput)
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
builder.setTitle("Add Configured Peer")
builder.setTitle(getString(R.string.peers_add_peer))
builder.setView(view)
builder.setPositiveButton("Add") { dialog, _ ->
builder.setPositiveButton(getString(R.string.peers_add)) { dialog, _ ->
config.updateJSON { json ->
json.getJSONArray("Peers").put(input.text.toString().trim())
}
dialog.dismiss()
updateConfiguredPeers()
}
builder.setNegativeButton("Cancel") { dialog, _ ->
builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
builder.show()
@ -70,22 +77,32 @@ class PeersActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
LocalBroadcastManager.getInstance(this).registerReceiver(
receiver, IntentFilter(PacketTunnelProvider.STATE_INTENT)
)
(application as GlobalApplication).subscribe()
updateConfiguredPeers()
updateConnectedPeers()
}
override fun onPause() {
super.onPause()
(application as GlobalApplication).unsubscribe()
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}
private fun updateConfiguredPeers() {
val peers = config.getJSON().getJSONArray("Peers")
when (peers.length()) {
0 -> {
configuredTableLayout.visibility = View.GONE
configuredTableLabel.text = "No peers currently configured"
configuredTableLabel.text = getString(R.string.peers_no_configured_title)
}
else -> {
configuredTableLayout.visibility = View.VISIBLE
configuredTableLabel.text = "Configured Peers"
configuredTableLabel.text = getString(R.string.peers_configured_title)
configuredTableLayout.removeAllViewsInLayout()
for (i in 0 until peers.length()) {
@ -96,15 +113,15 @@ class PeersActivity : AppCompatActivity() {
view.findViewById<ImageButton>(R.id.deletePeerButton).setOnClickListener { button ->
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setTitle("Remove ${peer}?")
builder.setPositiveButton("Remove") { dialog, _ ->
builder.setTitle(getString(R.string.peers_remove_title, peer))
builder.setPositiveButton(getString(R.string.peers_remove)) { dialog, _ ->
config.updateJSON { json ->
json.getJSONArray("Peers").remove(button.tag as Int)
}
dialog.dismiss()
updateConfiguredPeers()
}
builder.setNegativeButton("Cancel") { dialog, _ ->
builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
builder.show()
@ -116,20 +133,17 @@ class PeersActivity : AppCompatActivity() {
}
private fun updateConnectedPeers() {
val peers = state.peersState ?: JSONArray("[]")
when (peers.length()) {
when (peers.size) {
0 -> {
connectedTableLayout.visibility = View.GONE
connectedTableLabel.text = "No peers currently connected"
connectedTableLabel.text = getString(R.string.peers_no_connected_title)
}
else -> {
connectedTableLayout.visibility = View.VISIBLE
connectedTableLabel.text = "Connected Peers"
connectedTableLabel.text = getString(R.string.peers_connected_title)
connectedTableLayout.removeAllViewsInLayout()
for (i in 0 until peers.length()) {
val peer = peers.getJSONObject(i)
for (peer in peers) {
val view = inflater.inflate(R.layout.peers_connected, null)
val ip = peer.getString("IP")
view.findViewById<TextView>(R.id.addressLabel).text = ip
@ -139,4 +153,23 @@ class PeersActivity : AppCompatActivity() {
}
}
}
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
when (intent.getStringExtra("type")) {
"state" -> {
if (intent.hasExtra("peers")) {
val peersArray = JSONArray(intent.getStringExtra("peers") ?: "[]")
val array = Array(peersArray.length()) { i ->
peersArray.getJSONObject(i)
}
array.sortWith(compareBy { it.getString("IP") })
peers = array
updateConnectedPeers()
}
}
}
}
}
}

View file

@ -8,7 +8,6 @@ import android.os.Bundle
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.widget.*
import androidx.core.view.setPadding
import androidx.core.widget.doOnTextChanged
import org.json.JSONObject
@ -33,7 +32,7 @@ class SettingsActivity : AppCompatActivity() {
deviceNameEntry.doOnTextChanged { text, _, _, _ ->
config.updateJSON { cfg ->
var nodeinfo = cfg.optJSONObject("NodeInfo")
val nodeinfo = cfg.optJSONObject("NodeInfo")
if (nodeinfo == null) {
cfg.put("NodeInfo", JSONObject("{}"))
}
@ -42,16 +41,16 @@ class SettingsActivity : AppCompatActivity() {
}
resetConfigurationRow.setOnClickListener {
var view = inflater.inflate(R.layout.dialog_resetconfig, null)
val view = inflater.inflate(R.layout.dialog_resetconfig, null)
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
builder.setTitle("Warning")
builder.setTitle(getString(R.string.settings_warning_title))
builder.setView(view)
builder.setPositiveButton("Reset") { dialog, _ ->
builder.setPositiveButton(getString(R.string.settings_reset)) { dialog, _ ->
config.resetJSON()
updateView()
dialog.dismiss()
}
builder.setNegativeButton("Cancel") { dialog, _ ->
builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
builder.show()
@ -68,7 +67,7 @@ class SettingsActivity : AppCompatActivity() {
updateView()
}
fun updateView() {
private fun updateView() {
val nodeinfo = config.getJSON().optJSONObject("NodeInfo")
if (nodeinfo != null) {
deviceNameEntry.setText(nodeinfo.getString("name"), TextView.BufferType.EDITABLE)

View file

@ -18,4 +18,24 @@
<string name="dns_fix_chrome_based_browsers">Fix Chrome-based browsers</string>
<string name="dns_fix_chrome_based_browsers_hint">If you do not have IPv6 internet connectivity, this option should help Chrome-based browsers to resolve Yggdrasil domain names correctly.</string>
<string name="dns_fixes">DNS fixes</string>
<string name="dns_no_servers">No servers</string>
<string name="dns_one_server">1 server</string>
<string name="dns_many_servers">%d server</string>
<string name="dns_remove_title">Remove %s?</string>
<string name="main_no_connectivity">No connectivity</string>
<string name="main_enabled">Enabled</string>
<string name="main_disabled">Not enabled</string>
<string name="main_no_peers">No peers</string>
<string name="main_one_peer">1 peer</string>
<string name="main_many_peers">%d peers</string>
<string name="peers_add_peer">Add Configured Peer</string>
<string name="peers_add">Add</string>
<string name="peers_remove_title">Remove %s?</string>
<string name="peers_remove">Remove</string>
<string name="peers_no_configured_title">No peers currently configured</string>
<string name="peers_configured_title">Configured Peers</string>
<string name="peers_no_connected_title">No peers currently connected</string>
<string name="peers_connected_title">Connected Peers</string>
<string name="settings_warning_title">Warning</string>
<string name="settings_reset">Reset</string>
</resources>