Merge pull request #39 from vikulin/master

Bug fixes and improvements
This commit is contained in:
ChronosX88 2020-12-28 18:58:28 +03:00 committed by GitHub
commit 70fce5dda2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 545 additions and 80 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@
/yggdrasil/yggdrasil-sources.jar /yggdrasil/yggdrasil-sources.jar
/app/src/main/assets/yggdrasil-0.3.8-linux-arm64 /app/src/main/assets/yggdrasil-0.3.8-linux-arm64
/app/src/main/assets/yggdrasil-0.3.8-linux-armhf /app/src/main/assets/yggdrasil-0.3.8-linux-armhf
/.idea/

View File

@ -8,8 +8,8 @@ android {
applicationId "io.github.chronosx88.yggdrasil" applicationId "io.github.chronosx88.yggdrasil"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 30 targetSdkVersion 30
versionCode 5 versionCode 16
versionName "1.5" versionName "1.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
setProperty("archivesBaseName", project.getParent().name+"-"+versionName) setProperty("archivesBaseName", project.getParent().name+"-"+versionName)
} }
@ -42,6 +42,10 @@ android {
// but continue the build even when errors are found: // but continue the build even when errors are found:
abortOnError false abortOnError false
} }
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
}
} }
task ndkBuild(type: Exec) { task ndkBuild(type: Exec) {
@ -59,16 +63,18 @@ dependencies {
implementation project(path: ':yggdrasil') implementation project(path: ':yggdrasil')
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.preference:preference-ktx:1.1.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
implementation 'com.google.android.material:material:1.3.0-alpha02' implementation 'com.google.android.material:material:1.3.0-alpha04'
implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.hbb20:ccp:2.4.0' implementation 'com.hbb20:ccp:2.4.0'
implementation 'com.vincentbrison.openlibraries.android:dualcache:3.1.1'
implementation 'com.vincentbrison.openlibraries.android:dualcache-jsonserializer:3.1.1'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
} }

View File

@ -32,6 +32,12 @@
android:label="@string/title_activity_dns_list" android:label="@string/title_activity_dns_list"
android:theme="@style/AppTheme.NoActionBar" android:theme="@style/AppTheme.NoActionBar"
android:screenOrientation="portrait"/> android:screenOrientation="portrait"/>
<activity
android:name=".CopyLocalNodeInfoActivity"
android:parentActivityName=".MainActivity"
android:label="@string/title_activity_copy_local_node_info"
android:theme="@style/AppTheme.NoActionBar"
android:screenOrientation="portrait"/>
<service <service
android:name=".YggdrasilTunService" android:name=".YggdrasilTunService"
android:enabled="true" android:enabled="true"

View File

@ -0,0 +1,28 @@
package io.github.chronosx88.yggdrasil
import android.os.Bundle
import android.widget.ListView
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import io.github.chronosx88.yggdrasil.models.NodeInfo
import io.github.chronosx88.yggdrasil.models.config.CopyInfoAdapter
import io.github.chronosx88.yggdrasil.models.config.SelectDNSInfoListAdapter
class CopyLocalNodeInfoActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_copy_local_node_info)
setSupportActionBar(findViewById(R.id.toolbar))
val preferences =
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val ipv6Address = intent.extras!!.getString(MainActivity.IPv6, "")
val signingPublicKey = preferences.getString(MainActivity.signingPublicKey, "")
val encryptionPublicKey = preferences.getString(MainActivity.encryptionPublicKey, "")
var nodeInfoListView = findViewById<ListView>(R.id.nodeInfoList)
val nodeInfoList = listOf<NodeInfo>(NodeInfo("IP address", ipv6Address!!), NodeInfo("Encryption Public Key", encryptionPublicKey!!), NodeInfo("Signing Public Key", signingPublicKey!!));
var adapter = CopyInfoAdapter(this, nodeInfoList)
nodeInfoListView.adapter = adapter
}
}

View File

@ -59,11 +59,11 @@ class DNSListActivity : AppCompatActivity() {
try { try {
for (d in cd) { for (d in cd) {
var ping = ping(d.address, 53) var ping = ping(d.address.hostAddress, 53)
d.ping = ping d.ping = ping
} }
for (dns in allDNS) { for (dns in allDNS) {
var ping = ping(dns.address, 53) var ping = ping(dns.address.hostAddress, 53)
dns.ping = ping dns.ping = ping
runOnUiThread( runOnUiThread(
Runnable Runnable
@ -110,7 +110,7 @@ class DNSListActivity : AppCompatActivity() {
thread(start = true) { thread(start = true) {
var di = DNSInfo(InetAddress.getByName("["+ip+"]"), ccp, "User DNS") var di = DNSInfo(InetAddress.getByName("["+ip+"]"), ccp, "User DNS")
try { try {
var ping = ping(di.address, 53) var ping = ping(di.address.hostAddress, 53)
di.ping = ping di.ping = ping
} catch(e: Throwable){ } catch(e: Throwable){
di.ping = Int.MAX_VALUE di.ping = Int.MAX_VALUE
@ -127,7 +127,7 @@ class DNSListActivity : AppCompatActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.save, menu) menuInflater.inflate(R.menu.save_dns, menu)
val item = menu.findItem(R.id.saveItem) as MenuItem val item = menu.findItem(R.id.saveItem) as MenuItem
item.setActionView(R.layout.menu_save) item.setActionView(R.layout.menu_save)
val saveButton = item val saveButton = item

View File

@ -107,19 +107,21 @@ class MainActivity : AppCompatActivity() {
) )
val adapter = PeerInfoListAdapter(this, currentPeers.sortedWith(compareBy { it.ping })) val adapter = PeerInfoListAdapter(this, currentPeers.sortedWith(compareBy { it.ping }))
peersListView.adapter = adapter peersListView.adapter = adapter
if (adapter.count > 10) {
val item = adapter.getView(0, null, peersListView)
item.measure(0, 0)
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
(10 * item.measuredHeight).toInt()
)
peersListView.layoutParams = params
}
if(isStarted && this.currentPeers.isEmpty()) { if(isStarted && this.currentPeers.isEmpty()) {
updatePeers() updatePeers()
} }
val copyAddressButton = findViewById<Button>(R.id.copyIp)
copyAddressButton.setOnClickListener {
val ip = findViewById<TextView>(R.id.ip)
val clipboard: ClipboardManager =
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip =
ClipData.newPlainText("IP address", ip.text.toString())
clipboard.setPrimaryClip(clip)
showToast(getString(R.string.address_copied))
}
val editPeersButton = findViewById<Button>(R.id.edit) val editPeersButton = findViewById<Button>(R.id.edit)
editPeersButton.setOnClickListener { editPeersButton.setOnClickListener {
if(isStarted){ if(isStarted){
@ -150,6 +152,14 @@ class MainActivity : AppCompatActivity() {
intent.putStringArrayListExtra(DNS_LIST, serializeDNSInfoSet2StringList(currentDNS)) intent.putStringArrayListExtra(DNS_LIST, serializeDNSInfoSet2StringList(currentDNS))
startActivityForResult(intent, DNS_LIST_CODE) startActivityForResult(intent, DNS_LIST_CODE)
} }
val nodeInfoButton = findViewById<Button>(R.id.nodeInfo)
nodeInfoButton.setOnClickListener {
if(isStarted) {
val intent = Intent(this@MainActivity, CopyLocalNodeInfoActivity::class.java)
intent.putExtra(IPv6, findViewById<TextView>(R.id.ip).text.toString())
startActivity(intent)
}
}
if(isStarted){ if(isStarted){
val ipLayout = findViewById<LinearLayout>(R.id.ipLayout) val ipLayout = findViewById<LinearLayout>(R.id.ipLayout)
ipLayout.visibility = View.VISIBLE ipLayout.visibility = View.VISIBLE
@ -404,7 +414,7 @@ class MainActivity : AppCompatActivity() {
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
val preferences = val preferences =
PreferenceManager.getDefaultSharedPreferences(this.baseContext) PreferenceManager.getDefaultSharedPreferences(this.baseContext)
findViewById<Switch>(R.id.staticIP).isChecked = findViewById<SwitchCompat>(R.id.staticIP).isChecked =
preferences.getString(STATIC_IP, null) != null preferences.getString(STATIC_IP, null) != null
} }

View File

@ -5,16 +5,22 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import android.webkit.URLUtil
import android.widget.Button import android.widget.Button
import android.widget.ListView import android.widget.ListView
import android.widget.PopupWindow import android.widget.PopupWindow
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.hbb20.BuildConfig
import com.hbb20.CCPCountry import com.hbb20.CCPCountry
import com.vincentbrison.openlibraries.android.dualcache.Builder
import com.vincentbrison.openlibraries.android.dualcache.SizeOf
import com.vincentbrison.openlibraries.android.dualcache.JsonSerializer
import io.github.chronosx88.yggdrasil.models.PeerInfo import io.github.chronosx88.yggdrasil.models.PeerInfo
import io.github.chronosx88.yggdrasil.models.Status import io.github.chronosx88.yggdrasil.models.Status
import io.github.chronosx88.yggdrasil.models.config.DropDownAdapter import io.github.chronosx88.yggdrasil.models.config.DropDownAdapter
@ -22,23 +28,28 @@ import io.github.chronosx88.yggdrasil.models.config.SelectPeerInfoListAdapter
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2PeerInfoSet import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2PeerInfoSet
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.ping import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.ping
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializePeerInfoSet2StringList import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializePeerInfoSet2StringList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.lang.reflect.Type import java.lang.reflect.Type
import java.net.InetAddress import java.net.InetAddress
import java.net.URI import java.net.URI
import java.net.URL import java.net.URL
import java.net.UnknownHostException
import java.nio.charset.Charset import java.nio.charset.Charset
class PeerListActivity : AppCompatActivity() { class PeerListActivity : AppCompatActivity() {
companion object { companion object {
const val PEER_LIST = "PEER_LIST"
const val PEER_LIST_URL = "https://publicpeers.neilalexander.dev/publicnodes.json" const val PEER_LIST_URL = "https://publicpeers.neilalexander.dev/publicnodes.json"
const val CACHE_NAME = "PEER_LIST_CACHE"
const val ONLINE_PEERINFO_LIST = "online_peer_info_list"
const val OFFLINE_PEERINFO_LIST = "offline_peer_info_list"
const val TEST_APP_VERSION = BuildConfig.VERSION_CODE
const val RAM_MAX_SIZE = 100000
const val DISK_MAX_SIZE = 100000
} }
fun downloadJson(link: String): String { fun downloadJson(link: String): String {
@ -51,8 +62,8 @@ class PeerListActivity : AppCompatActivity() {
} }
} }
var isLoading = true; private var peerListUrl = PEER_LIST_URL
private var peerListPing = true
var popup: PopupWindow? = null var popup: PopupWindow? = null
var adapter: DropDownAdapter? = null var adapter: DropDownAdapter? = null
@ -63,26 +74,57 @@ class PeerListActivity : AppCompatActivity() {
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { _ -> findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { _ ->
addNewPeer() addNewPeer()
} }
val preferences =
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
var peerListUrl: String =
preferences.getString(PEER_LIST, "")!!
if(!peerListUrl.isNullOrBlank()){
this@PeerListActivity.peerListUrl = peerListUrl
}
var extras = intent.extras var extras = intent.extras
var peerList = findViewById<ListView>(R.id.peerList) var peerList = findViewById<ListView>(R.id.peerList)
var adapter = SelectPeerInfoListAdapter(this, arrayListOf(), mutableSetOf()) var adapter = SelectPeerInfoListAdapter(this, arrayListOf(), mutableSetOf())
peerList.adapter = adapter peerList.adapter = adapter
var peerInfoListCache = Builder<List<PeerInfo>>(CACHE_NAME, TEST_APP_VERSION)
.enableLog()
.useReferenceInRam(RAM_MAX_SIZE, SizeOfPeerList())
.useSerializerInDisk(
DISK_MAX_SIZE, true,
JsonSerializer(ArrayList<PeerInfo>().javaClass), baseContext
).build();
GlobalScope.launch { GlobalScope.launch() {
try { try {
var cp = deserializeStringList2PeerInfoSet( var cp = deserializeStringList2PeerInfoSet(
extras!!.getStringArrayList(MainActivity.PEER_LIST)!! extras!!.getStringArrayList(MainActivity.PEER_LIST)!!
) )
for(pi in cp){ for (pi in cp) {
var ping = ping(pi.address, pi.port) var ping = ping(pi.hostName, pi.port)
pi.ping = ping pi.ping = ping
} }
try { try {
var json = downloadJson(PEER_LIST_URL) var peerInfoCache = peerInfoListCache.get(ONLINE_PEERINFO_LIST)
if (peerInfoCache != null && peerInfoCache.isNotEmpty()) {
for (peerInfo in peerInfoCache) {
var ping = ping(peerInfo.hostName, peerInfo.port)
peerInfo.ping = ping
if (cp.contains(peerInfo)) {
continue
}
withContext(Dispatchers.Main) {
adapter.addItem(peerInfo)
if (adapter.count % 5 == 0) {
adapter.sort()
}
}
}
}
var json = downloadJson(this@PeerListActivity.peerListUrl)
var countries = CCPCountry.getLibraryMasterCountriesEnglish() var countries = CCPCountry.getLibraryMasterCountriesEnglish()
val mapType: Type = object : val mapType: Type = object :
TypeToken<Map<String?, Map<String, Status>>>() {}.type TypeToken<Map<String?, Map<String, Status>>>() {}.type
val peersMap: Map<String, Map<String, Status>> = Gson().fromJson(json, mapType) val peersMap: Map<String, Map<String, Status>> = Gson().fromJson(json, mapType)
var cachePeerInfoList = mutableListOf<PeerInfo>()
for ((country, peers) in peersMap.entries) { for ((country, peers) in peersMap.entries) {
for ((peer, status) in peers) { for ((peer, status) in peers) {
if (status.up) { if (status.up) {
@ -90,23 +132,40 @@ class PeerListActivity : AppCompatActivity() {
if (ccp.name.toLowerCase() if (ccp.name.toLowerCase()
.contains(country.replace(".md", "").replace("-", " ")) .contains(country.replace(".md", "").replace("-", " "))
) { ) {
if(!peerListPing){
return@launch
}
var url = URI(peer) var url = URI(peer)
try { try {
var address = InetAddress.getByName(url.host) var address = InetAddress.getByName(url.host)
var peerInfo = var peerInfo =
PeerInfo(url.scheme, address, url.port, ccp.nameCode) PeerInfo(
var ping = ping(address, url.port) url.scheme,
address,
url.port,
ccp.nameCode
)
var ping = ping(url.host, url.port)
peerInfo.ping = ping peerInfo.ping = ping
if(cp.contains(peerInfo)){ if (cp.contains(peerInfo)) {
continue continue
} }
if (peerInfo.ping < Int.MAX_VALUE) {
cachePeerInfoList.add(peerInfo)
}
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
adapter.addItem(peerInfo) adapter.addItem(peerInfo)
if(adapter.count % 5 == 0) { if (adapter.count % 5 == 0) {
adapter.sort() adapter.sort()
if (cachePeerInfoList.size > 0) {
peerInfoListCache.put(
ONLINE_PEERINFO_LIST,
cachePeerInfoList.toList()
)
}
} }
} }
} catch (e: Throwable){ } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
} }
} }
@ -114,21 +173,63 @@ class PeerListActivity : AppCompatActivity() {
} }
} }
} }
} catch(e: FileNotFoundException){ } catch (e: Exception) {
e.printStackTrace() when (e) {
is FileNotFoundException, is UnknownHostException -> {
var onlinePeerInfoList = peerInfoListCache.get(ONLINE_PEERINFO_LIST)
if (onlinePeerInfoList != null) {
for (peerInfo in onlinePeerInfoList) {
var ping = ping(peerInfo.hostName, peerInfo.port)
peerInfo.ping = ping
if (cp.contains(peerInfo)) {
continue
}
withContext(Dispatchers.Main) {
adapter.addItem(peerInfo)
if (adapter.count % 5 == 0) {
adapter.sort()
}
}
}
}
e.printStackTrace()
}
else -> e.printStackTrace()
}
} }
var currentPeers = ArrayList(cp.sortedWith(compareBy { it.ping })) var currentPeers = ArrayList(cp.sortedWith(compareBy { it.ping }))
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
adapter.addAll(0, currentPeers) adapter.addAll(0, currentPeers)
isLoading = false
adapter.setLoading(isLoading)
} }
} catch (e: Throwable){ } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
} }
} }
} }
private fun editPeerListUrl() {
val view: View = LayoutInflater.from(this).inflate(R.layout.edit_peer_list_url_dialog, null)
val ab: AlertDialog.Builder = AlertDialog.Builder(this)
ab.setCancelable(true).setView(view)
var ad = ab.show()
var saveButton = view.findViewById<Button>(R.id.save)
var urlInput = view.findViewById<TextView>(R.id.urlInput)
urlInput.text = peerListUrl
saveButton.setOnClickListener{
var url = urlInput.text.toString()
if(!URLUtil.isValidUrl(url)){
urlInput.error = "The URL is invalid!"
return@setOnClickListener;
}
peerListUrl = url
val preferences =
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
preferences.edit().putString(PEER_LIST, peerListUrl).apply()
ad.dismiss()
}
}
private fun addNewPeer() { private fun addNewPeer() {
val view: View = LayoutInflater.from(this).inflate(R.layout.new_peer_dialog, null) val view: View = LayoutInflater.from(this).inflate(R.layout.new_peer_dialog, null)
val countryCode: String = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val countryCode: String = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@ -150,7 +251,11 @@ class PeerListActivity : AppCompatActivity() {
schemaInput.setOnClickListener { v-> schemaInput.setOnClickListener { v->
onClickSchemaList(v) onClickSchemaList(v)
} }
getPopupWindow(R.layout.spinner_item, resources.getStringArray(R.array.schemas), schemaInput); getPopupWindow(
R.layout.spinner_item,
resources.getStringArray(R.array.schemas),
schemaInput
);
view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp).setCountryForNameCode(countryCode) view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp).setCountryForNameCode(countryCode)
val ab: AlertDialog.Builder = AlertDialog.Builder(this) val ab: AlertDialog.Builder = AlertDialog.Builder(this)
ab.setCancelable(true).setView(view) ab.setCancelable(true).setView(view)
@ -178,9 +283,9 @@ class PeerListActivity : AppCompatActivity() {
GlobalScope.launch { GlobalScope.launch {
var pi = PeerInfo(schema, InetAddress.getByName(ip), port, ccp) var pi = PeerInfo(schema, InetAddress.getByName(ip), port, ccp)
try { try {
var ping = ping(pi.address, pi.port) var ping = ping(pi.hostName, pi.port)
pi.ping = ping pi.ping = ping
} catch(e: Throwable){ } catch (e: Throwable){
pi.ping = Int.MAX_VALUE pi.ping = Int.MAX_VALUE
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -229,15 +334,14 @@ class PeerListActivity : AppCompatActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.save, menu) menuInflater.inflate(R.menu.save_peers, menu)
val item = menu.findItem(R.id.saveItem) as MenuItem val item = menu.findItem(R.id.saveItem) as MenuItem
item.setActionView(R.layout.menu_save) item.setActionView(R.layout.menu_save)
val saveButton = item val saveButton = item
.actionView.findViewById<Button>(R.id.saveButton) .actionView.findViewById<Button>(R.id.saveButton)
saveButton.setOnClickListener { saveButton.setOnClickListener {
if(isLoading){ saveButton.isClickable = false
return@setOnClickListener cancelPeerListPing()
}
val result = Intent(this, MainActivity::class.java) val result = Intent(this, MainActivity::class.java)
var adapter = findViewById<ListView>(R.id.peerList).adapter as SelectPeerInfoListAdapter var adapter = findViewById<ListView>(R.id.peerList).adapter as SelectPeerInfoListAdapter
val selectedPeers = adapter.getSelectedPeers() val selectedPeers = adapter.getSelectedPeers()
@ -245,6 +349,45 @@ class PeerListActivity : AppCompatActivity() {
setResult(Activity.RESULT_OK, result) setResult(Activity.RESULT_OK, result)
finish() finish()
} }
val editUrl = menu.findItem(R.id.editUrlItem) as MenuItem
editUrl.setActionView(R.layout.menu_edit_url)
val editUrlButton = editUrl
.actionView.findViewById<Button>(R.id.editUrlButton)
editUrlButton.setOnClickListener {
editPeerListUrl()
}
return true return true
} }
private fun cancelPeerListPing() {
peerListPing = false
}
override fun onStop() {
super.onStop()
cancelPeerListPing()
}
}
class SizeOfPeerList: SizeOf<List<PeerInfo>> {
override fun sizeOf(obj: List<PeerInfo>): Int{
var size = 0
for (o in obj) {
if (o.hostName != null) {
size += o.hostName.length * 2
}
if (o.schema != null) {
size += o.schema.length * 2
}
if (o.countryCode != null) {
size += o.countryCode!!.length * 2
}
size += 4
size += 4
size += 1
}
return size
}
} }

View File

@ -177,7 +177,7 @@ class YggdrasilTunService : VpnService() {
.putString(MainActivity.signingPublicKey, signingPublicKey) .putString(MainActivity.signingPublicKey, signingPublicKey)
.putString(MainActivity.encryptionPrivateKey, encryptionPrivateKey) .putString(MainActivity.encryptionPrivateKey, encryptionPrivateKey)
.putString(MainActivity.encryptionPublicKey, encryptionPublicKey) .putString(MainActivity.encryptionPublicKey, encryptionPublicKey)
.putString(MainActivity.STATIC_IP,MainActivity.STATIC_IP).apply() .putString(MainActivity.STATIC_IP, MainActivity.STATIC_IP).apply()
} else { } else {
val signingPrivateKey = preferences.getString(MainActivity.signingPrivateKey, null) val signingPrivateKey = preferences.getString(MainActivity.signingPrivateKey, null)
val signingPublicKey = preferences.getString(MainActivity.signingPublicKey, null) val signingPublicKey = preferences.getString(MainActivity.signingPublicKey, null)

View File

@ -0,0 +1,13 @@
package io.github.chronosx88.yggdrasil.models
class NodeInfo {
constructor(key: String, value: String){
this.key = key
this.value = value
}
var key: String
var value: String
}

View File

@ -7,9 +7,13 @@ import java.net.InetAddress
class PeerInfo { class PeerInfo {
constructor(){
}
constructor(schema: String, address: InetAddress, port: Int, countryCode: String){ constructor(schema: String, address: InetAddress, port: Int, countryCode: String){
this.schema = schema this.schema = schema
this.address = address
var a = address.toString(); var a = address.toString();
if(a.lastIndexOf('/')>0){ if(a.lastIndexOf('/')>0){
this.hostName = a.split("/")[0] this.hostName = a.split("/")[0]
@ -22,7 +26,6 @@ class PeerInfo {
constructor(schema: String, address: InetAddress, port: Int, countryCode: String?, isMeshPeer: Boolean){ constructor(schema: String, address: InetAddress, port: Int, countryCode: String?, isMeshPeer: Boolean){
this.schema = schema this.schema = schema
this.address = address
var a = address.toString(); var a = address.toString();
if(a.lastIndexOf('/')>0){ if(a.lastIndexOf('/')>0){
this.hostName = a.split("/")[0] this.hostName = a.split("/")[0]
@ -34,9 +37,8 @@ class PeerInfo {
this.isMeshPeer = isMeshPeer this.isMeshPeer = isMeshPeer
} }
var schema: String lateinit var schema: String
var address: InetAddress lateinit var hostName: String
var hostName: String
var port = 0 var port = 0
var countryCode: String?=null var countryCode: String?=null
var ping: Int = Int.MAX_VALUE var ping: Int = Int.MAX_VALUE

View File

@ -0,0 +1,67 @@
package io.github.chronosx88.yggdrasil.models.config
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import io.github.chronosx88.yggdrasil.R
import io.github.chronosx88.yggdrasil.models.NodeInfo
class CopyInfoAdapter(
context: Context,
nodeInfoList: List<NodeInfo>,
) : ArrayAdapter<NodeInfo?> (context, 0, nodeInfoList) {
private val mContext: Context = context
private var nodeInfoList: MutableList<NodeInfo> = nodeInfoList as MutableList<NodeInfo>
override fun getItem(position: Int): NodeInfo? {
return nodeInfoList[position]
}
override fun getCount(): Int {
return nodeInfoList.size
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var copyNodeInfoHolder = CopyInfoHolder()
var listItem: View? = convertView
if (listItem == null) {
listItem = LayoutInflater.from(mContext).inflate(R.layout.copy_node_info_list_item, parent, false)
copyNodeInfoHolder.copyButton = listItem.findViewById(R.id.nodeInfoButton) as Button
copyNodeInfoHolder.nodeInfoText = listItem.findViewById(R.id.nodeInfoText) as TextView
copyNodeInfoHolder.nodeInfoKey = listItem.findViewById(R.id.nodeInfoKey) as TextView
listItem.tag = copyNodeInfoHolder
} else {
copyNodeInfoHolder = listItem.tag as CopyInfoHolder
}
copyNodeInfoHolder.nodeInfoKey.text = nodeInfoList[position].key
copyNodeInfoHolder.nodeInfoText.text = nodeInfoList[position].value
copyNodeInfoHolder.copyButton.setOnClickListener{ _ ->
val clipboard: ClipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip =
ClipData.newPlainText(nodeInfoList[position].key, nodeInfoList[position].value)
clipboard.setPrimaryClip(clip)
showToast(nodeInfoList[position].key + " " + context.getString(R.string.node_info_copied))
}
return listItem!!
}
private fun showToast(text: String){
val duration = Toast.LENGTH_SHORT
val toast = Toast.makeText(context, text, duration)
toast.setGravity(Gravity.CENTER, 0, 0)
toast.show()
}
class CopyInfoHolder {
lateinit var nodeInfoKey: TextView
lateinit var nodeInfoText: TextView
lateinit var copyButton: Button
}
}

View File

@ -1,14 +1,14 @@
package io.github.chronosx88.yggdrasil.models.config package io.github.chronosx88.yggdrasil.models.config
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.*
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import io.github.chronosx88.yggdrasil.R import io.github.chronosx88.yggdrasil.R
import io.github.chronosx88.yggdrasil.models.DNSInfo import io.github.chronosx88.yggdrasil.models.DNSInfo
@ -66,6 +66,14 @@ class SelectDNSInfoListAdapter(
} }
} }
} }
dnsInfoHolder.dnsInfoText.setOnClickListener {
val clipboard: ClipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip =
ClipData.newPlainText("DNS info", dnsId)
clipboard.setPrimaryClip(clip)
showToast(dnsId + " " + context.getString(R.string.node_info_copied))
}
dnsInfoHolder.checkbox.isChecked = this.currentDNS.contains(currentDNS) dnsInfoHolder.checkbox.isChecked = this.currentDNS.contains(currentDNS)
return listItem!! return listItem!!
} }
@ -100,4 +108,11 @@ class SelectDNSInfoListAdapter(
lateinit var dnsInfoText: TextView lateinit var dnsInfoText: TextView
lateinit var ping: TextView lateinit var ping: TextView
} }
private fun showToast(text: String){
val duration = Toast.LENGTH_SHORT
val toast = Toast.makeText(context, text, duration)
toast.setGravity(Gravity.CENTER, 0, 0)
toast.show()
}
} }

View File

@ -1,14 +1,14 @@
package io.github.chronosx88.yggdrasil.models.config package io.github.chronosx88.yggdrasil.models.config
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.*
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import io.github.chronosx88.yggdrasil.R import io.github.chronosx88.yggdrasil.R
import io.github.chronosx88.yggdrasil.models.PeerInfo import io.github.chronosx88.yggdrasil.models.PeerInfo
@ -18,7 +18,6 @@ class SelectPeerInfoListAdapter(
currentPeers: MutableSet<PeerInfo> currentPeers: MutableSet<PeerInfo>
) : ArrayAdapter<PeerInfo?> (context, 0, allPeers) { ) : ArrayAdapter<PeerInfo?> (context, 0, allPeers) {
private var isLoading = true
private val mContext: Context = context private val mContext: Context = context
private var allPeers: MutableList<PeerInfo> = allPeers as MutableList<PeerInfo> private var allPeers: MutableList<PeerInfo> = allPeers as MutableList<PeerInfo>
private var currentPeers: MutableSet<PeerInfo> = currentPeers private var currentPeers: MutableSet<PeerInfo> = currentPeers
@ -47,12 +46,11 @@ class SelectPeerInfoListAdapter(
val currentPeer = allPeers[position] val currentPeer = allPeers[position]
peerInfoHolder.countryFlag.setImageResource(currentPeer.getCountry(mContext)!!.flagID) peerInfoHolder.countryFlag.setImageResource(currentPeer.getCountry(mContext)!!.flagID)
val peerId = currentPeer.toString() val peerId = currentPeer.toString()
peerInfoHolder.peerInfoText.text = peerId
if(currentPeer.ping == Int.MAX_VALUE){ if(currentPeer.ping == Int.MAX_VALUE){
peerInfoHolder.peerInfoText.text = peerId
peerInfoHolder.ping.text="" peerInfoHolder.ping.text=""
peerInfoHolder.peerInfoText.setTextColor(Color.GRAY) peerInfoHolder.peerInfoText.setTextColor(Color.GRAY)
} else { } else {
peerInfoHolder.peerInfoText.text = peerId
peerInfoHolder.ping.text = currentPeer.ping.toString() + " ms" peerInfoHolder.ping.text = currentPeer.ping.toString() + " ms"
peerInfoHolder.peerInfoText.setTextColor(Color.WHITE) peerInfoHolder.peerInfoText.setTextColor(Color.WHITE)
} }
@ -67,6 +65,14 @@ class SelectPeerInfoListAdapter(
} }
} }
} }
peerInfoHolder.peerInfoText.setOnClickListener {
val clipboard: ClipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip =
ClipData.newPlainText("Peer info", peerId)
clipboard.setPrimaryClip(clip)
showToast(peerId + " " + context.getString(R.string.node_info_copied))
}
peerInfoHolder.checkbox.isChecked = this.currentPeers.contains(currentPeer) peerInfoHolder.checkbox.isChecked = this.currentPeers.contains(currentPeer)
return listItem!! return listItem!!
} }
@ -75,8 +81,14 @@ class SelectPeerInfoListAdapter(
return currentPeers return currentPeers
} }
fun getAllPeers(): List<PeerInfo> {
return allPeers
}
fun addItem(peerInfo: PeerInfo){ fun addItem(peerInfo: PeerInfo){
allPeers.add(peerInfo) if(!allPeers.contains(peerInfo)){
allPeers.add(peerInfo)
}
} }
fun addItem(index: Int, peerInfo: PeerInfo){ fun addItem(index: Int, peerInfo: PeerInfo){
@ -95,10 +107,6 @@ class SelectPeerInfoListAdapter(
this.notifyDataSetChanged() this.notifyDataSetChanged()
} }
fun setLoading(loading: Boolean){
this.isLoading = loading
}
class PeerInfoHolder { class PeerInfoHolder {
lateinit var checkbox: CheckBox lateinit var checkbox: CheckBox
lateinit var countryFlag: ImageView lateinit var countryFlag: ImageView
@ -106,4 +114,10 @@ class SelectPeerInfoListAdapter(
lateinit var ping: TextView lateinit var ping: TextView
} }
private fun showToast(text: String){
val duration = Toast.LENGTH_SHORT
val toast = Toast.makeText(context, text, duration)
toast.setGravity(Gravity.CENTER, 0, 0)
toast.show()
}
} }

View File

@ -77,15 +77,15 @@ class Utils {
} }
@JvmStatic @JvmStatic
fun ping(address: InetAddress, port:Int): Int { fun ping(hostname: String, port:Int): Int {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val socket = Socket() val socket = Socket()
try { try {
socket.connect(InetSocketAddress(address, port), 5000) socket.connect(InetSocketAddress(hostname, port), 5000)
socket.close() socket.close()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
print(address) print(hostname)
return Int.MAX_VALUE return Int.MAX_VALUE
} }
return (System.currentTimeMillis() - start).toInt() return (System.currentTimeMillis() - start).toInt()

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CopyLocalNodeInfoActivity"
android:background="@color/grey">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_node_info" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -35,10 +35,10 @@
android:textColor="@color/dark_30" android:textColor="@color/dark_30"
/> />
<Button <Button
android:id="@+id/copyIp" android:id="@+id/nodeInfo"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="COPY" android:text="INFO"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:background="@android:color/transparent"/> android:background="@android:color/transparent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@ -53,7 +53,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/copyIp" app:layout_constraintEnd_toStartOf="@+id/nodeInfo"
android:text="" android:text=""
android:textColor="@color/white"/> android:textColor="@color/white"/>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<ListView
android:id="@+id/nodeInfoList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/dark_20"
android:dividerHeight="2px"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,52 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_toLeftOf="@+id/nodeInfoButton"
android:id="@+id/data">
<TextView
android:id="@+id/nodeInfoKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textSize="10sp"
android:text = "nodeInfoKey"
android:textColor="@color/white"
android:layout_marginStart="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@+id/nodeInfoText"
android:layout_marginLeft="10dp"
android:paddingBottom="5dp"/>
<TextView
android:id="@+id/nodeInfoText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:gravity="center_vertical"
android:minHeight="35dp"
android:text="nodeInfoText"
android:textColor="@color/white"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/nodeInfoKey"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/nodeInfoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="COPY"
android:textColor="@color/white"
android:layout_alignParentRight="true"
android:background="@android:color/transparent"/>
</RelativeLayout>

View File

@ -0,0 +1,43 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="10dp"
android:background="@color/grey">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="10dp"
app:boxBackgroundMode="none"
android:background="@drawable/edit_text_rounded_corner"
android:textColorHint="@color/white"
style="@style/EditText.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/urlInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="URL"
android:textColor="@color/white"
android:textCursorDrawable="@null"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/save"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
app:layout_constraintTop_toBottomOf="@+id/url"
android:background="@drawable/button_selector"
app:backgroundTint="@null"
android:text="SAVE"
android:textColor="@color/white"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/editUrlButton"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="EDIT URL"
android:background="@android:color/transparent"/>
</RelativeLayout>

View File

@ -0,0 +1,12 @@
<?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/editUrlItem"
android:title=""
app:actionLayout="@layout/menu_edit_url"
app:showAsAction="always"/>
<item android:id="@+id/saveItem"
android:title=""
app:actionLayout="@layout/menu_save"
app:showAsAction="always"/>
</menu>

View File

@ -5,7 +5,8 @@
<string name="switch_button_title">SwitchOn</string> <string name="switch_button_title">SwitchOn</string>
<string name="title_activity_peer_list">Edit peers</string> <string name="title_activity_peer_list">Edit peers</string>
<string name="title_activity_dns_list">Edit DNS</string> <string name="title_activity_dns_list">Edit DNS</string>
<string name="address_copied">Address copied</string> <string name="node_info_copied">copied</string>
<string name="schema">Schema</string> <string name="schema">Schema</string>
<string name="title_activity_about">Yggdrasil</string> <string name="title_activity_about">Yggdrasil</string>
<string name="title_activity_copy_local_node_info">Node info</string>
</resources> </resources>

View File

@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.72' ext.kotlin_version = '1.4.10'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@ -4,7 +4,7 @@ all:
-go get -u github.com/yggdrasil-network/yggdrasil-go; -go get -u github.com/yggdrasil-network/yggdrasil-go;
-cd $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go; \ -cd $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go; \
go get -v -d ./...; \ go get -v -d ./...; \
go get -u github.com/vikulin/yggdrasil-extras@268b006; \ go get -u github.com/yggdrasil-network/yggdrasil-extras@005d79c; \
ANDROID=true ./build; ANDROID=true ./build;
mv -f $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go/yggdrasil.aar yggdrasil.aar; mv -f $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go/yggdrasil.aar yggdrasil.aar;
mv -f $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go/yggdrasil-sources.jar yggdrasil-sources.jar; mv -f $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go/yggdrasil-sources.jar yggdrasil-sources.jar;