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
/app/src/main/assets/yggdrasil-0.3.8-linux-arm64
/app/src/main/assets/yggdrasil-0.3.8-linux-armhf
/.idea/

View File

@ -8,8 +8,8 @@ android {
applicationId "io.github.chronosx88.yggdrasil"
minSdkVersion 15
targetSdkVersion 30
versionCode 5
versionName "1.5"
versionCode 16
versionName "1.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
setProperty("archivesBaseName", project.getParent().name+"-"+versionName)
}
@ -42,6 +42,10 @@ android {
// but continue the build even when errors are found:
abortOnError false
}
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
}
}
task ndkBuild(type: Exec) {
@ -59,16 +63,18 @@ dependencies {
implementation project(path: ':yggdrasil')
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 "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
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.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.espresso:espresso-core:3.3.0'
}

View File

@ -32,6 +32,12 @@
android:label="@string/title_activity_dns_list"
android:theme="@style/AppTheme.NoActionBar"
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
android:name=".YggdrasilTunService"
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 {
for (d in cd) {
var ping = ping(d.address, 53)
var ping = ping(d.address.hostAddress, 53)
d.ping = ping
}
for (dns in allDNS) {
var ping = ping(dns.address, 53)
var ping = ping(dns.address.hostAddress, 53)
dns.ping = ping
runOnUiThread(
Runnable
@ -110,7 +110,7 @@ class DNSListActivity : AppCompatActivity() {
thread(start = true) {
var di = DNSInfo(InetAddress.getByName("["+ip+"]"), ccp, "User DNS")
try {
var ping = ping(di.address, 53)
var ping = ping(di.address.hostAddress, 53)
di.ping = ping
} catch(e: Throwable){
di.ping = Int.MAX_VALUE
@ -127,7 +127,7 @@ class DNSListActivity : AppCompatActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// 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
item.setActionView(R.layout.menu_save)
val saveButton = item

View File

@ -107,19 +107,21 @@ class MainActivity : AppCompatActivity() {
)
val adapter = PeerInfoListAdapter(this, currentPeers.sortedWith(compareBy { it.ping }))
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()) {
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)
editPeersButton.setOnClickListener {
if(isStarted){
@ -150,6 +152,14 @@ class MainActivity : AppCompatActivity() {
intent.putStringArrayListExtra(DNS_LIST, serializeDNSInfoSet2StringList(currentDNS))
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){
val ipLayout = findViewById<LinearLayout>(R.id.ipLayout)
ipLayout.visibility = View.VISIBLE
@ -404,7 +414,7 @@ class MainActivity : AppCompatActivity() {
super.onRestoreInstanceState(savedInstanceState)
val preferences =
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
findViewById<Switch>(R.id.staticIP).isChecked =
findViewById<SwitchCompat>(R.id.staticIP).isChecked =
preferences.getString(STATIC_IP, null) != null
}

View File

@ -5,16 +5,22 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.*
import android.webkit.URLUtil
import android.widget.Button
import android.widget.ListView
import android.widget.PopupWindow
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.hbb20.BuildConfig
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.Status
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.ping
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializePeerInfoSet2StringList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import java.io.ByteArrayOutputStream
import java.io.FileNotFoundException
import java.lang.reflect.Type
import java.net.InetAddress
import java.net.URI
import java.net.URL
import java.net.UnknownHostException
import java.nio.charset.Charset
class PeerListActivity : AppCompatActivity() {
companion object {
const val PEER_LIST = "PEER_LIST"
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 {
@ -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 adapter: DropDownAdapter? = null
@ -63,26 +74,57 @@ class PeerListActivity : AppCompatActivity() {
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { _ ->
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 peerList = findViewById<ListView>(R.id.peerList)
var adapter = SelectPeerInfoListAdapter(this, arrayListOf(), mutableSetOf())
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 {
var cp = deserializeStringList2PeerInfoSet(
extras!!.getStringArrayList(MainActivity.PEER_LIST)!!
)
for(pi in cp){
var ping = ping(pi.address, pi.port)
for (pi in cp) {
var ping = ping(pi.hostName, pi.port)
pi.ping = ping
}
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()
val mapType: Type = object :
TypeToken<Map<String?, Map<String, Status>>>() {}.type
val peersMap: Map<String, Map<String, Status>> = Gson().fromJson(json, mapType)
var cachePeerInfoList = mutableListOf<PeerInfo>()
for ((country, peers) in peersMap.entries) {
for ((peer, status) in peers) {
if (status.up) {
@ -90,23 +132,40 @@ class PeerListActivity : AppCompatActivity() {
if (ccp.name.toLowerCase()
.contains(country.replace(".md", "").replace("-", " "))
) {
if(!peerListPing){
return@launch
}
var url = URI(peer)
try {
var address = InetAddress.getByName(url.host)
var peerInfo =
PeerInfo(url.scheme, address, url.port, ccp.nameCode)
var ping = ping(address, url.port)
PeerInfo(
url.scheme,
address,
url.port,
ccp.nameCode
)
var ping = ping(url.host, url.port)
peerInfo.ping = ping
if(cp.contains(peerInfo)){
if (cp.contains(peerInfo)) {
continue
}
if (peerInfo.ping < Int.MAX_VALUE) {
cachePeerInfoList.add(peerInfo)
}
withContext(Dispatchers.Main) {
adapter.addItem(peerInfo)
if(adapter.count % 5 == 0) {
if (adapter.count % 5 == 0) {
adapter.sort()
if (cachePeerInfoList.size > 0) {
peerInfoListCache.put(
ONLINE_PEERINFO_LIST,
cachePeerInfoList.toList()
)
}
}
}
} catch (e: Throwable){
} catch (e: Throwable) {
e.printStackTrace()
}
}
@ -114,21 +173,63 @@ class PeerListActivity : AppCompatActivity() {
}
}
}
} catch(e: FileNotFoundException){
e.printStackTrace()
} catch (e: Exception) {
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 }))
withContext(Dispatchers.Main) {
adapter.addAll(0, currentPeers)
isLoading = false
adapter.setLoading(isLoading)
}
} catch (e: Throwable){
} catch (e: Throwable) {
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() {
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) {
@ -150,7 +251,11 @@ class PeerListActivity : AppCompatActivity() {
schemaInput.setOnClickListener { 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)
val ab: AlertDialog.Builder = AlertDialog.Builder(this)
ab.setCancelable(true).setView(view)
@ -178,9 +283,9 @@ class PeerListActivity : AppCompatActivity() {
GlobalScope.launch {
var pi = PeerInfo(schema, InetAddress.getByName(ip), port, ccp)
try {
var ping = ping(pi.address, pi.port)
var ping = ping(pi.hostName, pi.port)
pi.ping = ping
} catch(e: Throwable){
} catch (e: Throwable){
pi.ping = Int.MAX_VALUE
}
withContext(Dispatchers.Main) {
@ -229,15 +334,14 @@ class PeerListActivity : AppCompatActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// 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
item.setActionView(R.layout.menu_save)
val saveButton = item
.actionView.findViewById<Button>(R.id.saveButton)
saveButton.setOnClickListener {
if(isLoading){
return@setOnClickListener
}
saveButton.isClickable = false
cancelPeerListPing()
val result = Intent(this, MainActivity::class.java)
var adapter = findViewById<ListView>(R.id.peerList).adapter as SelectPeerInfoListAdapter
val selectedPeers = adapter.getSelectedPeers()
@ -245,6 +349,45 @@ class PeerListActivity : AppCompatActivity() {
setResult(Activity.RESULT_OK, result)
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
}
}
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.encryptionPrivateKey, encryptionPrivateKey)
.putString(MainActivity.encryptionPublicKey, encryptionPublicKey)
.putString(MainActivity.STATIC_IP,MainActivity.STATIC_IP).apply()
.putString(MainActivity.STATIC_IP, MainActivity.STATIC_IP).apply()
} else {
val signingPrivateKey = preferences.getString(MainActivity.signingPrivateKey, 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 {
constructor(){
}
constructor(schema: String, address: InetAddress, port: Int, countryCode: String){
this.schema = schema
this.address = address
var a = address.toString();
if(a.lastIndexOf('/')>0){
this.hostName = a.split("/")[0]
@ -22,7 +26,6 @@ class PeerInfo {
constructor(schema: String, address: InetAddress, port: Int, countryCode: String?, isMeshPeer: Boolean){
this.schema = schema
this.address = address
var a = address.toString();
if(a.lastIndexOf('/')>0){
this.hostName = a.split("/")[0]
@ -34,9 +37,8 @@ class PeerInfo {
this.isMeshPeer = isMeshPeer
}
var schema: String
var address: InetAddress
var hostName: String
lateinit var schema: String
lateinit var hostName: String
var port = 0
var countryCode: String?=null
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
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Color
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import android.widget.*
import io.github.chronosx88.yggdrasil.R
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)
return listItem!!
}
@ -100,4 +108,11 @@ class SelectDNSInfoListAdapter(
lateinit var dnsInfoText: 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
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Color
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import android.widget.*
import io.github.chronosx88.yggdrasil.R
import io.github.chronosx88.yggdrasil.models.PeerInfo
@ -18,7 +18,6 @@ class SelectPeerInfoListAdapter(
currentPeers: MutableSet<PeerInfo>
) : ArrayAdapter<PeerInfo?> (context, 0, allPeers) {
private var isLoading = true
private val mContext: Context = context
private var allPeers: MutableList<PeerInfo> = allPeers as MutableList<PeerInfo>
private var currentPeers: MutableSet<PeerInfo> = currentPeers
@ -47,12 +46,11 @@ class SelectPeerInfoListAdapter(
val currentPeer = allPeers[position]
peerInfoHolder.countryFlag.setImageResource(currentPeer.getCountry(mContext)!!.flagID)
val peerId = currentPeer.toString()
peerInfoHolder.peerInfoText.text = peerId
if(currentPeer.ping == Int.MAX_VALUE){
peerInfoHolder.peerInfoText.text = peerId
peerInfoHolder.ping.text=""
peerInfoHolder.peerInfoText.setTextColor(Color.GRAY)
} else {
peerInfoHolder.peerInfoText.text = peerId
peerInfoHolder.ping.text = currentPeer.ping.toString() + " ms"
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)
return listItem!!
}
@ -75,8 +81,14 @@ class SelectPeerInfoListAdapter(
return currentPeers
}
fun getAllPeers(): List<PeerInfo> {
return allPeers
}
fun addItem(peerInfo: PeerInfo){
allPeers.add(peerInfo)
if(!allPeers.contains(peerInfo)){
allPeers.add(peerInfo)
}
}
fun addItem(index: Int, peerInfo: PeerInfo){
@ -95,10 +107,6 @@ class SelectPeerInfoListAdapter(
this.notifyDataSetChanged()
}
fun setLoading(loading: Boolean){
this.isLoading = loading
}
class PeerInfoHolder {
lateinit var checkbox: CheckBox
lateinit var countryFlag: ImageView
@ -106,4 +114,10 @@ class SelectPeerInfoListAdapter(
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
fun ping(address: InetAddress, port:Int): Int {
fun ping(hostname: String, port:Int): Int {
val start = System.currentTimeMillis()
val socket = Socket()
try {
socket.connect(InetSocketAddress(address, port), 5000)
socket.connect(InetSocketAddress(hostname, port), 5000)
socket.close()
} catch (e: Exception) {
e.printStackTrace()
print(address)
print(hostname)
return Int.MAX_VALUE
}
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"
/>
<Button
android:id="@+id/copyIp"
android:id="@+id/nodeInfo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="COPY"
android:text="INFO"
app:layout_constraintEnd_toEndOf="parent"
android:background="@android:color/transparent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@ -53,7 +53,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/copyIp"
app:layout_constraintEnd_toStartOf="@+id/nodeInfo"
android:text=""
android:textColor="@color/white"/>
</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="title_activity_peer_list">Edit peers</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="title_activity_about">Yggdrasil</string>
<string name="title_activity_copy_local_node_info">Node info</string>
</resources>

View File

@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.72'
ext.kotlin_version = '1.4.10'
repositories {
google()
mavenCentral()
jcenter()
}
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"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -4,7 +4,7 @@ all:
-go get -u github.com/yggdrasil-network/yggdrasil-go;
-cd $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go; \
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;
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;