Skip to content

Commit 055a74e

Browse files
authored
UI improvements (#74)
* Fixed connected peers status in PeersActivity. * Fixed display of new generated public key in SettingsActivity. * Made more reliable display of connectivity state on main screen. * Added a note about not configured peers. Changed all dialog buttons to greenish color. * Click on a version row now opens URL of the project on GitHub. * Changed the wording of no peer notification.
1 parent 94db1fa commit 055a74e

File tree

11 files changed

+109
-29
lines changed

11 files changed

+109
-29
lines changed

app/src/main/java/eu/neilalexander/yggdrasil/ConfigurationProxy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ object ConfigurationProxy {
5555
json.put("IfMTU", 65535)
5656

5757
if (json.getJSONArray("MulticastInterfaces").get(0) is String) {
58-
var ar = JSONArray()
58+
val ar = JSONArray()
5959
ar.put(0, JSONObject("""
6060
{
6161
"Regex": ".*",

app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class DnsActivity : AppCompatActivity() {
5757
addServerButton.setOnClickListener {
5858
val view = inflater.inflate(R.layout.dialog_add_dns_server, null)
5959
val input = view.findViewById<TextInputEditText>(R.id.addDnsInput)
60-
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
60+
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
6161
builder.setTitle(getString(R.string.dns_add_server_dialog_title))
6262
builder.setView(view)
6363
builder.setPositiveButton(getString(R.string.add)) { _, _ ->
@@ -128,7 +128,7 @@ class DnsActivity : AppCompatActivity() {
128128
view.findViewById<ImageButton>(R.id.deletePeerButton).tag = i
129129

130130
view.findViewById<ImageButton>(R.id.deletePeerButton).setOnClickListener { button ->
131-
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
131+
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
132132
builder.setTitle(getString(R.string.dns_remove_title, server))
133133
builder.setPositiveButton(getString(R.string.remove)) { dialog, _ ->
134134
servers.removeAt(button.tag as Int)
@@ -176,7 +176,7 @@ class DnsActivity : AppCompatActivity() {
176176
}
177177
}
178178
view.setOnLongClickListener {
179-
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
179+
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
180180
builder.setTitle(getString(R.string.dns_server_info_dialog_title))
181181
builder.setMessage("${infoPair.first}\n\n${infoPair.second}")
182182
builder.setPositiveButton(getString(R.string.ok)) { dialog, _ ->

app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.core.app.NotificationCompat
1111
import androidx.preference.PreferenceManager
1212

1313
const val PREF_KEY_ENABLED = "enabled"
14+
const val PREF_KEY_PEERS_NOTE = "peers_note"
1415
const val MAIN_CHANNEL_ID = "Yggdrasil Service"
1516

1617
class GlobalApplication: Application(), YggStateReceiver.StateReceiver {

app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package eu.neilalexander.yggdrasil
22

33
import android.app.Activity
4+
import android.app.AlertDialog
45
import android.content.*
56
import android.graphics.Color
7+
import android.net.Uri
68
import android.net.VpnService
79
import android.os.Bundle
10+
import android.view.ContextThemeWrapper
811
import android.widget.Switch
912
import android.widget.TextView
1013
import android.widget.Toast
@@ -18,6 +21,7 @@ import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT
1821
import mobile.Mobile
1922
import org.json.JSONArray
2023

24+
const val APP_WEB_URL = "https://github.com/yggdrasil-network/yggdrasil-android"
2125

2226
class MainActivity : AppCompatActivity() {
2327
private lateinit var enabledSwitch: Switch
@@ -29,6 +33,7 @@ class MainActivity : AppCompatActivity() {
2933
private lateinit var dnsLabel: TextView
3034
private lateinit var dnsRow: LinearLayoutCompat
3135
private lateinit var settingsRow: LinearLayoutCompat
36+
private lateinit var versionRow: LinearLayoutCompat
3237

3338
private fun start() {
3439
val intent = Intent(this, PacketTunnelProvider::class.java)
@@ -57,6 +62,7 @@ class MainActivity : AppCompatActivity() {
5762
dnsLabel = findViewById(R.id.dnsValue)
5863
dnsRow = findViewById(R.id.dnsTableRow)
5964
settingsRow = findViewById(R.id.settingsTableRow)
65+
versionRow = findViewById(R.id.versionTableRow)
6066

6167
enabledLabel.setTextColor(Color.GRAY)
6268

@@ -104,6 +110,11 @@ class MainActivity : AppCompatActivity() {
104110
startActivity(intent)
105111
}
106112

113+
versionRow.isClickable = true
114+
versionRow.setOnClickListener {
115+
openUrlInBrowser(APP_WEB_URL)
116+
}
117+
107118
ipAddressLabel.setOnLongClickListener {
108119
val clipboard: ClipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
109120
val clip = ClipData.newPlainText("ip", ipAddressLabel.text)
@@ -152,15 +163,16 @@ class MainActivity : AppCompatActivity() {
152163
override fun onReceive(context: Context?, intent: Intent) {
153164
when (intent.getStringExtra("type")) {
154165
"state" -> {
155-
enabledLabel.text = if (intent.getBooleanExtra("started", false)) {
156-
var count = 0
157-
if (intent.hasExtra("peers")) {
158-
val peers = intent.getStringExtra("peers")
159-
if (peers != null && peers != "null") {
160-
val peerState = JSONArray(peers)
161-
count = peerState.length()
162-
}
166+
val peerState = JSONArray(intent.getStringExtra("peers") ?: "[]")
167+
var count = 0
168+
for (i in 0..<peerState.length()) {
169+
val peer = peerState.getJSONObject(i)
170+
if (peer.getString("IP").isNotEmpty()) {
171+
count += 1
163172
}
173+
}
174+
enabledLabel.text = if (intent.getBooleanExtra("started", false)) {
175+
showPeersNoteIfNeeded(peerState.length())
164176
if (count == 0) {
165177
enabledLabel.setTextColor(Color.RED)
166178
getString(R.string.main_no_connectivity)
@@ -175,8 +187,7 @@ class MainActivity : AppCompatActivity() {
175187
ipAddressLabel.text = intent.getStringExtra("ip") ?: "N/A"
176188
subnetLabel.text = intent.getStringExtra("subnet") ?: "N/A"
177189
if (intent.hasExtra("peers")) {
178-
val peerState = JSONArray(intent.getStringExtra("peers") ?: "[]")
179-
peersLabel.text = when (val count = peerState.length()) {
190+
peersLabel.text = when (count) {
180191
0 -> getString(R.string.main_no_peers)
181192
1 -> getString(R.string.main_one_peer)
182193
else -> getString(R.string.main_many_peers, count)
@@ -189,4 +200,38 @@ class MainActivity : AppCompatActivity() {
189200
}
190201
}
191202
}
203+
204+
private fun showPeersNoteIfNeeded(peerCount: Int) {
205+
if (peerCount > 0) return
206+
val preferences = PreferenceManager.getDefaultSharedPreferences(this@MainActivity.baseContext)
207+
if (!preferences.getBoolean(PREF_KEY_PEERS_NOTE, false)) {
208+
this@MainActivity.runOnUiThread {
209+
val builder: AlertDialog.Builder =
210+
AlertDialog.Builder(ContextThemeWrapper(this@MainActivity, R.style.YggdrasilDialogs))
211+
builder.setTitle(getString(R.string.main_add_some_peers_title))
212+
builder.setMessage(getString(R.string.main_add_some_peers_message))
213+
builder.setPositiveButton(getString(R.string.ok)) { dialog, _ ->
214+
dialog.dismiss()
215+
}
216+
builder.show()
217+
}
218+
// Mark this note as shown
219+
preferences.edit().apply {
220+
putBoolean(PREF_KEY_PEERS_NOTE, true)
221+
commit()
222+
}
223+
}
224+
}
225+
226+
fun openUrlInBrowser(url: String) {
227+
val intent = Intent(Intent.ACTION_VIEW).apply {
228+
data = Uri.parse(url)
229+
}
230+
try {
231+
startActivity(intent)
232+
} catch (e: ActivityNotFoundException) {
233+
// Handle the exception if no browser is found
234+
Toast.makeText(this, getText(R.string.no_browser_found_toast), Toast.LENGTH_SHORT).show()
235+
}
236+
}
192237
}

app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,27 @@ import android.content.BroadcastReceiver
55
import android.content.Context
66
import android.content.Intent
77
import android.content.IntentFilter
8-
import androidx.appcompat.app.AppCompatActivity
98
import android.os.Bundle
109
import android.text.method.LinkMovementMethod
1110
import android.util.Log
1211
import android.view.ContextThemeWrapper
1312
import android.view.KeyEvent
1413
import android.view.LayoutInflater
1514
import android.view.View
16-
import android.widget.*
15+
import android.widget.EditText
16+
import android.widget.ImageButton
17+
import android.widget.Switch
18+
import android.widget.TableLayout
19+
import android.widget.TableRow
20+
import android.widget.TextView
21+
import androidx.appcompat.app.AppCompatActivity
1722
import androidx.core.widget.doOnTextChanged
1823
import androidx.localbroadcastmanager.content.LocalBroadcastManager
1924
import com.google.android.material.textfield.TextInputEditText
2025
import org.json.JSONArray
2126
import org.json.JSONObject
2227

28+
2329
class PeersActivity : AppCompatActivity() {
2430
private lateinit var config: ConfigurationProxy
2531
private lateinit var inflater: LayoutInflater
@@ -99,7 +105,7 @@ class PeersActivity : AppCompatActivity() {
99105
addPeerButton.setOnClickListener {
100106
val view = inflater.inflate(R.layout.dialog_addpeer, null)
101107
val input = view.findViewById<TextInputEditText>(R.id.addPeerInput)
102-
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
108+
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
103109
builder.setTitle(getString(R.string.peers_add_peer))
104110
builder.setView(view)
105111
builder.setPositiveButton(getString(R.string.peers_add)) { dialog, _ ->
@@ -153,7 +159,7 @@ class PeersActivity : AppCompatActivity() {
153159
view.findViewById<ImageButton>(R.id.deletePeerButton).tag = i
154160

155161
view.findViewById<ImageButton>(R.id.deletePeerButton).setOnClickListener { button ->
156-
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
162+
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
157163
builder.setTitle(getString(R.string.peers_remove_title, peer))
158164
builder.setPositiveButton(getString(R.string.peers_remove)) { dialog, _ ->
159165
config.updateJSON { json ->
@@ -180,16 +186,25 @@ class PeersActivity : AppCompatActivity() {
180186
connectedTableLabel.text = getString(R.string.peers_no_connected_title)
181187
}
182188
else -> {
183-
connectedTableLayout.visibility = View.VISIBLE
184-
connectedTableLabel.text = getString(R.string.peers_connected_title)
185-
189+
var connected = false
186190
connectedTableLayout.removeAllViewsInLayout()
187191
for (peer in peers) {
188192
val view = inflater.inflate(R.layout.peers_connected, null)
189193
val ip = peer.getString("IP")
190-
view.findViewById<TextView>(R.id.addressLabel).text = ip
191-
view.findViewById<TextView>(R.id.detailsLabel).text = peer.getString("URI")
192-
connectedTableLayout.addView(view)
194+
// Only connected peers have IPs
195+
if (ip.isNotEmpty()) {
196+
view.findViewById<TextView>(R.id.addressLabel).text = ip
197+
view.findViewById<TextView>(R.id.detailsLabel).text = peer.getString("URI")
198+
connectedTableLayout.addView(view)
199+
connected = true
200+
}
201+
}
202+
if (connected) {
203+
connectedTableLayout.visibility = View.VISIBLE
204+
connectedTableLabel.text = getString(R.string.peers_connected_title)
205+
} else {
206+
connectedTableLayout.visibility = View.GONE
207+
connectedTableLabel.text = getString(R.string.peers_no_connected_title)
193208
}
194209
}
195210
}

app/src/main/java/eu/neilalexander/yggdrasil/SettingsActivity.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class SettingsActivity : AppCompatActivity() {
2727
private lateinit var deviceNameEntry: EditText
2828
private lateinit var publicKeyLabel: TextView
2929
private lateinit var resetConfigurationRow: LinearLayoutCompat
30+
private var publicKeyReset = false
3031

3132
override fun onCreate(savedInstanceState: Bundle?) {
3233
super.onCreate(savedInstanceState)
@@ -69,7 +70,7 @@ class SettingsActivity : AppCompatActivity() {
6970

7071
resetConfigurationRow.setOnClickListener {
7172
val view = inflater.inflate(R.layout.dialog_resetconfig, null)
72-
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
73+
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
7374
builder.setTitle(getString(R.string.settings_warning_title))
7475
builder.setView(view)
7576
builder.setPositiveButton(getString(R.string.settings_reset)) { dialog, _ ->
@@ -85,12 +86,13 @@ class SettingsActivity : AppCompatActivity() {
8586

8687
findViewById<View>(R.id.resetKeysRow).setOnClickListener {
8788
config.resetKeys()
89+
publicKeyReset = true
8890
updateView()
8991
}
9092

9193
findViewById<View>(R.id.setKeysRow).setOnClickListener {
9294
val view = inflater.inflate(R.layout.dialog_set_keys, null)
93-
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
95+
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
9496
val privateKey = view.findViewById<EditText>(R.id.private_key)
9597
builder.setTitle(getString(R.string.set_keys))
9698
builder.setView(view)
@@ -125,7 +127,11 @@ class SettingsActivity : AppCompatActivity() {
125127
deviceNameEntry.setText("", TextView.BufferType.EDITABLE)
126128
}
127129

128-
publicKeyLabel.text = json.optString("PublicKey")
130+
var key = json.optString("PrivateKey")
131+
if (key.isNotEmpty()) {
132+
key = key.substring(key.length / 2)
133+
}
134+
publicKeyLabel.text = key
129135
}
130136

131137
override fun onResume() {
@@ -145,7 +151,7 @@ class SettingsActivity : AppCompatActivity() {
145151
// To be able to get public key from running Yggdrasil we use this receiver, as we don't have this field in config
146152
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
147153
override fun onReceive(context: Context?, intent: Intent) {
148-
if (intent.hasExtra("pubkey")) {
154+
if (intent.hasExtra("pubkey") && !publicKeyReset) {
149155
val tree = intent.getStringExtra("pubkey")
150156
if (tree != null && tree != "null") {
151157
publicKeyLabel.text = intent.getStringExtra("pubkey")

app/src/main/res/layout/activity_main.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,9 @@
351351

352352
</androidx.appcompat.widget.LinearLayoutCompat>
353353

354-
<androidx.appcompat.widget.LinearLayoutCompat style="@style/SelectableItemStyle">
354+
<androidx.appcompat.widget.LinearLayoutCompat
355+
android:id="@+id/versionTableRow"
356+
style="@style/SelectableItemStyle">
355357

356358
<TextView
357359
android:id="@+id/versionLabel"

app/src/main/res/values-ru/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
<string name="main_no_peers">Нет пиров</string>
3030
<string name="main_one_peer">1 пир</string>
3131
<string name="main_many_peers">%d пира/пиров</string>
32+
<string name="main_add_some_peers_title">Внимание</string>
33+
<string name="main_add_some_peers_message">Не настроено ни одного пира. Если не будет обнаруживаемых пиров в этой сети, то вам надо будет добавить пир вручную, чтобы подключение к Yggdrasil работало как положено.</string>
3234
<string name="peers_add_peer">Добавить пира в конфиг</string>
3335
<string name="peers_add">Добавить</string>
3436
<string name="peers_remove_title">Убрать %s?</string>
@@ -83,4 +85,5 @@
8385
<string name="private_key_label">Приватный ключ:</string>
8486
<string name="set_keys">Установить свой ключ</string>
8587
<string name="save">Сохранить</string>
88+
<string name="no_browser_found_toast">Не найден браузер для открытия ссылки!</string>
8689
</resources>

app/src/main/res/values/colors.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<color name="purple_700">#FF3700B3</color>
66
<color name="teal_200">#FF03DAC5</color>
77
<color name="teal_700">#FF018786</color>
8+
<color name="green">#5FBF9F</color>
89
<color name="black">#FF000000</color>
910
<color name="white">#FFFFFFFF</color>
1011
<color name="hintlight">#F2F1F5</color>

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
<string name="main_no_peers">No peers</string>
3030
<string name="main_one_peer">1 peer</string>
3131
<string name="main_many_peers">%d peers</string>
32+
<string name="main_add_some_peers_title">Note</string>
33+
<string name="main_add_some_peers_message">No peers are configured. If there are no multicast peers nearby, you will need to manually configure peers in order for Yggdrasil to connect and work properly.</string>
3234
<string name="peers_add_peer">Add Configured Peer</string>
3335
<string name="peers_add">Add</string>
3436
<string name="peers_remove_title">Remove %s?</string>
@@ -83,4 +85,5 @@
8385
<string name="private_key_label">Private key:</string>
8486
<string name="set_keys">Set your own key</string>
8587
<string name="save">Save</string>
88+
<string name="no_browser_found_toast">No browser found to open the URL!</string>
8689
</resources>

0 commit comments

Comments
 (0)