mirror of
https://github.com/yggdrasil-network/crispa-android.git
synced 2025-01-22 07:56:30 +00:00
Merge pull request #10 from vikulin/master
Added more functionality: peers dynamic list sorted by ping, DNS list, Static IP
This commit is contained in:
commit
1997f260c4
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@ -12,7 +12,6 @@
|
|||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
<option value="$PROJECT_DIR$/yggdrasil" />
|
<option value="$PROJECT_DIR$/yggdrasil" />
|
||||||
<option value="$PROJECT_DIR$/yggdrasil.aar" />
|
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveModulePerSourceSet" value="false" />
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
@ -6,15 +6,15 @@ android {
|
|||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.github.chronosx88.yggdrasil"
|
applicationId "io.github.chronosx88.yggdrasil"
|
||||||
minSdkVersion 22
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName "1.0"
|
versionName "1.2"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
project.ext.set("archivesBaseName", project.getParent().name+"-"+versionName)
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
|
|
||||||
storeFile file(KEYSTORE_FILE_PATH)
|
storeFile file(KEYSTORE_FILE_PATH)
|
||||||
storePassword System.getenv("KEYSTORE_PASSWORD")
|
storePassword System.getenv("KEYSTORE_PASSWORD")
|
||||||
keyAlias System.getenv("KEY_ALIAS")
|
keyAlias System.getenv("KEY_ALIAS")
|
||||||
@ -29,19 +29,41 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ndkVersion "21.2.6472646"
|
ndkVersion "21.2.6472646"
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task ndkBuild(type: Exec) {
|
||||||
|
def rootDir = project.rootDir
|
||||||
|
workingDir = new File(rootDir,"yggdrasil")
|
||||||
|
commandLine 'make'
|
||||||
|
}
|
||||||
|
|
||||||
|
gradle.projectsEvaluated {
|
||||||
|
tasks.compileDebugKotlin.dependsOn(ndkBuild)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation project(path: ':yggdrasil')
|
implementation project(path: ':yggdrasil')
|
||||||
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
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-alpha01'
|
||||||
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
|
implementation 'com.hbb20:ccp:2.4.0'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-RC2'
|
}
|
||||||
implementation 'com.google.android.material:material:1.1.0-alpha09'
|
|
||||||
implementation 'org.hjson:hjson:3.0.0'
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.6'
|
|
||||||
}
|
|
@ -2,30 +2,47 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="io.github.chronosx88.yggdrasil">
|
package="io.github.chronosx88.yggdrasil">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
|
||||||
tools:ignore="GoogleAppIndexingWarning">
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
|
<activity
|
||||||
|
android:name=".PeerListActivity"
|
||||||
|
android:label="@string/title_activity_peer_list"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
|
android:screenOrientation="portrait"/>
|
||||||
|
<activity
|
||||||
|
android:name=".DNSListActivity"
|
||||||
|
android:label="@string/title_activity_dns_list"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
|
android:screenOrientation="portrait"/>
|
||||||
<service
|
<service
|
||||||
android:name=".YggdrasilTunService"
|
android:name=".YggdrasilTunService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.net.VpnService"/>
|
<action android:name="android.net.VpnService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<activity android:name=".MainActivity">
|
|
||||||
|
<activity android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
|
android:screenOrientation="portrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -1,3 +0,0 @@
|
|||||||
package io.github.chronosx88.yggdrasil
|
|
||||||
|
|
||||||
const val YGGDRASIL_VERSION = "0.3.8"
|
|
@ -0,0 +1,150 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.*
|
||||||
|
import android.widget.*
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.SelectDNSInfoListAdapter
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2DNSInfoSet
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.ping
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializeDNSInfoSet2StringList
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import java.net.InetAddress
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|
||||||
|
class DNSListActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val allDNS = arrayListOf(
|
||||||
|
DNSInfo(
|
||||||
|
InetAddress.getByName("[301:2522::53]"),
|
||||||
|
"CZ",
|
||||||
|
"DNS implementation for Yggdrasil. https://github.com/Revertron/wyrd"),
|
||||||
|
DNSInfo(InetAddress.getByName("[301:2923::53]"),
|
||||||
|
"SK",
|
||||||
|
"DNS implementation for Yggdrasil. https://github.com/Revertron/wyrd"),
|
||||||
|
DNSInfo(InetAddress.getByName("[300:4523::53]"),
|
||||||
|
"DE",
|
||||||
|
"DNS implementation for Yggdrasil. https://github.com/Revertron/wyrd"),
|
||||||
|
DNSInfo(InetAddress.getByName("[303:8b1a::53]"),
|
||||||
|
"RU",
|
||||||
|
"DNS implementation for Yggdrasil. https://github.com/Revertron/wyrd")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isLoading = true;
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_dns_list)
|
||||||
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
|
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view ->
|
||||||
|
addNewDNS()
|
||||||
|
}
|
||||||
|
var extras = intent.extras
|
||||||
|
var dnsList = findViewById<ListView>(R.id.dnsList)
|
||||||
|
var adapter = SelectDNSInfoListAdapter(this, arrayListOf(), mutableSetOf())
|
||||||
|
dnsList.adapter = adapter
|
||||||
|
thread(start = true) {
|
||||||
|
try {
|
||||||
|
var cd = deserializeStringList2DNSInfoSet(
|
||||||
|
extras!!.getStringArrayList(MainActivity.DNS_LIST)!!
|
||||||
|
)
|
||||||
|
for (d in cd) {
|
||||||
|
var ping = ping(d.address, 53)
|
||||||
|
d.ping = ping
|
||||||
|
}
|
||||||
|
for (dns in allDNS) {
|
||||||
|
if (cd.contains(dns)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var ping = ping(dns.address, 53)
|
||||||
|
dns.ping = ping
|
||||||
|
runOnUiThread(
|
||||||
|
Runnable
|
||||||
|
{
|
||||||
|
adapter.addItem(dns)
|
||||||
|
adapter.sort()
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addNewDNS() {
|
||||||
|
val view: View = LayoutInflater.from(this).inflate(R.layout.new_dns_dialog, null)
|
||||||
|
val countryCode: String = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
this.resources.configuration.locales[0].country
|
||||||
|
} else {
|
||||||
|
this.resources.configuration.locale.country
|
||||||
|
}
|
||||||
|
|
||||||
|
var ccp = view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp)
|
||||||
|
ccp.setCountryForNameCode(countryCode)
|
||||||
|
val ab: AlertDialog.Builder = AlertDialog.Builder(this)
|
||||||
|
ab.setCancelable(true).setView(view)
|
||||||
|
var ad = ab.show()
|
||||||
|
var addButton = view.findViewById<Button>(R.id.add)
|
||||||
|
addButton.setOnClickListener{
|
||||||
|
var ipInput = view.findViewById<TextView>(R.id.ipInput)
|
||||||
|
var ccpInput = view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp)
|
||||||
|
var ip = ipInput.text.toString().toLowerCase()
|
||||||
|
var ccp = ccpInput.selectedCountryNameCode
|
||||||
|
GlobalScope.launch {
|
||||||
|
var di = DNSInfo(InetAddress.getByName("["+ip+"]"), ccp, "User DNS")
|
||||||
|
try {
|
||||||
|
var ping = ping(di.address, 53)
|
||||||
|
di.ping = ping
|
||||||
|
} catch(e: Throwable){
|
||||||
|
di.ping = Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
var selectAdapter = (findViewById<ListView>(R.id.peerList).adapter as SelectDNSInfoListAdapter)
|
||||||
|
selectAdapter.addItem(0, di)
|
||||||
|
selectAdapter.notifyDataSetChanged()
|
||||||
|
ad.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
val result = Intent(this, MainActivity::class.java)
|
||||||
|
var adapter = findViewById<ListView>(R.id.dnsList).adapter as SelectDNSInfoListAdapter
|
||||||
|
val selectedDNS = adapter.getSelectedDNS()
|
||||||
|
if(selectedDNS.isNotEmpty()) {
|
||||||
|
result.putExtra(MainActivity.DNS_LIST, serializeDNSInfoSet2StringList(selectedDNS))
|
||||||
|
setResult(Activity.RESULT_OK, result)
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
val text = "Select at least one DNS"
|
||||||
|
val duration = Toast.LENGTH_SHORT
|
||||||
|
val toast = Toast.makeText(applicationContext, text, duration)
|
||||||
|
toast.setGravity(Gravity.CENTER, 0, 0)
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1,78 +1,340 @@
|
|||||||
package io.github.chronosx88.yggdrasil
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
import android.R.attr
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.PendingIntent
|
import android.app.ActivityManager
|
||||||
import android.content.Intent
|
import android.content.*
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.RadioGroup
|
import android.view.Gravity
|
||||||
import android.widget.TextView
|
import android.view.View
|
||||||
|
import android.widget.*
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.DNSInfoListAdapter
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.PeerInfoListAdapter
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializePeerStringList2PeerInfoSet
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2DNSInfoSet
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2PeerInfoSet
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringSet2DNSInfoSet
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringSet2PeerInfoSet
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializeDNSInfoSet2StringList
|
||||||
|
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 java.net.InetAddress
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val STATUS_PEERS_UPDATE = 12
|
||||||
|
const val MESH_PEERS = "MESH_PEERS"
|
||||||
|
const val STATIC_IP = "STATIC_IP_FLAG"
|
||||||
|
const val signingPrivateKey = "signingPrivateKey"
|
||||||
|
const val signingPublicKey = "signingPublicKey"
|
||||||
|
const val encryptionPrivateKey = "encryptionPrivateKey"
|
||||||
|
const val encryptionPublicKey = "encryptionPublicKey"
|
||||||
|
const val COMMAND = "COMMAND"
|
||||||
|
const val STOP = "STOP"
|
||||||
|
const val START = "START"
|
||||||
|
const val UPDATE_DNS = "UPDATE_DNS"
|
||||||
|
const val UPDATE_PEERS = "UPDATE_PEERS"
|
||||||
const val PARAM_PINTENT = "pendingIntent"
|
const val PARAM_PINTENT = "pendingIntent"
|
||||||
const val STATUS_START = 1
|
const val STATUS_START = 7
|
||||||
const val STATUS_FINISH = 0
|
const val STATUS_FINISH = 8
|
||||||
|
const val STATUS_STOP = 9
|
||||||
const val IPv6: String = "IPv6"
|
const val IPv6: String = "IPv6"
|
||||||
|
const val PEERS: String = "PEERS"
|
||||||
|
const val DNS: String = "DNS"
|
||||||
|
const val PEER_LIST_CODE = 1000
|
||||||
|
const val DNS_LIST_CODE = 2000
|
||||||
|
const val PEER_LIST = "PEERS_LIST"
|
||||||
|
const val DNS_LIST = "DNS_LIST"
|
||||||
|
const val CURRENT_PEERS = "CURRENT_PEERS_v1.2.1"
|
||||||
|
const val CURRENT_DNS = "CURRENT_DNS_v1.2"
|
||||||
|
const val START_VPN = "START_VPN"
|
||||||
private const val TAG="Yggdrasil"
|
private const val TAG="Yggdrasil"
|
||||||
private const val VPN_REQUEST_CODE = 0x0F
|
private const val VPN_REQUEST_CODE = 0x0F
|
||||||
|
|
||||||
|
@JvmStatic var isStarted = false
|
||||||
|
@JvmStatic var isCancelled = false
|
||||||
|
@JvmStatic var address = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var currentPeers = setOf<PeerInfo>()
|
||||||
|
private var currentDNS = setOf<DNSInfo>()
|
||||||
|
private var meshPeersReceiver: BroadcastReceiver? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
val connectRadioGroup = findViewById<RadioGroup>(R.id.connectRadioGroup)
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
connectRadioGroup.setOnCheckedChangeListener { group, checkedId ->
|
isStarted = isYggServiceRunning(this)
|
||||||
when (checkedId) {
|
val switchOn = findViewById<Switch>(R.id.switchOn)
|
||||||
R.id.disconnectButton -> stopVpn()
|
switchOn.isChecked = isStarted
|
||||||
R.id.connectButton -> startVpn()
|
|
||||||
else -> { // Note the block
|
|
||||||
|
|
||||||
}
|
switchOn.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if(isCancelled){
|
||||||
|
switchOn.isChecked = false
|
||||||
|
isCancelled = false
|
||||||
|
return@setOnCheckedChangeListener
|
||||||
|
}
|
||||||
|
if (isChecked) {
|
||||||
|
startVpn()
|
||||||
|
} else {
|
||||||
|
stopVpn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//save to shared preferences
|
||||||
|
val preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
|
val staticIP = findViewById<Switch>(R.id.staticIP)
|
||||||
|
staticIP.isChecked =
|
||||||
|
preferences.getString(STATIC_IP, null) != null
|
||||||
|
val peersListView = findViewById<ListView>(R.id.peers)
|
||||||
|
|
||||||
|
currentPeers = deserializeStringSet2PeerInfoSet(preferences.getStringSet(CURRENT_PEERS, HashSet())!!)
|
||||||
|
val adapter = PeerInfoListAdapter(this, currentPeers.sortedWith(compareBy { it.ping }))
|
||||||
|
peersListView.adapter = adapter
|
||||||
|
|
||||||
|
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){
|
||||||
|
showToast("Service is running. Please stop service before edit Peers list")
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
val intent = Intent(this@MainActivity, PeerListActivity::class.java)
|
||||||
|
intent.putStringArrayListExtra(PEER_LIST, serializePeerInfoSet2StringList(currentPeers))
|
||||||
|
startActivityForResult(intent, PEER_LIST_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val listViewDNS = findViewById<ListView>(R.id.dns)
|
||||||
|
currentDNS = deserializeStringSet2DNSInfoSet(preferences.getStringSet(CURRENT_DNS, HashSet())!!)
|
||||||
|
val adapterDns = DNSInfoListAdapter(this, currentDNS.sortedWith(compareBy { it.ping }))
|
||||||
|
listViewDNS.adapter = adapterDns
|
||||||
|
val editDnsButton = findViewById<Button>(R.id.editDNS)
|
||||||
|
editDnsButton.setOnClickListener {
|
||||||
|
if(!isStarted){
|
||||||
|
showToast("Service is not running. DNS ping will not be run")
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
val intent = Intent(this@MainActivity, DNSListActivity::class.java)
|
||||||
|
intent.putStringArrayListExtra(DNS_LIST, serializeDNSInfoSet2StringList(currentDNS))
|
||||||
|
startActivityForResult(intent, DNS_LIST_CODE)
|
||||||
|
}
|
||||||
|
if(isStarted){
|
||||||
|
val ipLayout = findViewById<LinearLayout>(R.id.ipLayout)
|
||||||
|
ipLayout.visibility = View.VISIBLE
|
||||||
|
findViewById<TextView>(R.id.ip).text = address
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopVpn(){
|
private fun stopVpn(){
|
||||||
Log.d(TAG,"Stop")
|
Log.d(TAG,"Stop")
|
||||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
intent.putExtra("COMMAND", "STOP")
|
val TASK_CODE = 100
|
||||||
|
val pi = createPendingResult(TASK_CODE, intent, 0)
|
||||||
|
intent.putExtra(PARAM_PINTENT, pi)
|
||||||
|
intent.putExtra(COMMAND, STOP)
|
||||||
startService(intent)
|
startService(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startVpn(){
|
private fun startVpn(){
|
||||||
Log.d(TAG,"Start")
|
Log.d(TAG,"Start")
|
||||||
val intent= VpnService.prepare(this)
|
val intent= VpnService.prepare(this)
|
||||||
if (intent!=null){
|
if (intent!=null){
|
||||||
startActivityForResult(intent, VPN_REQUEST_CODE);
|
startActivityForResult(intent, VPN_REQUEST_CODE)
|
||||||
}else{
|
}else{
|
||||||
onActivityResult(VPN_REQUEST_CODE, Activity.RESULT_OK, null);
|
onActivityResult(VPN_REQUEST_CODE, Activity.RESULT_OK, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateDNS(){
|
||||||
|
Log.d(TAG,"Update DNS")
|
||||||
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
|
val TASK_CODE = 100
|
||||||
|
val pi = createPendingResult(TASK_CODE, intent, 0)
|
||||||
|
intent.putExtra(PARAM_PINTENT, pi)
|
||||||
|
intent.putExtra(COMMAND, UPDATE_DNS)
|
||||||
|
intent.putStringArrayListExtra(DNS, serializeDNSInfoSet2StringList(currentDNS))
|
||||||
|
startService(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePeers(){
|
||||||
|
Log.d(TAG,"Update Peers")
|
||||||
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
|
val TASK_CODE = 100
|
||||||
|
val pi = createPendingResult(TASK_CODE, intent, 0)
|
||||||
|
intent.putExtra(PARAM_PINTENT, pi)
|
||||||
|
intent.putExtra(COMMAND, UPDATE_PEERS)
|
||||||
|
startService(intent)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
if (requestCode == VPN_REQUEST_CODE && resultCode== Activity.RESULT_OK){
|
if (requestCode == VPN_REQUEST_CODE && resultCode== Activity.RESULT_OK){
|
||||||
|
|
||||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
val TASK_CODE = 100
|
val TASK_CODE = 100
|
||||||
var pi = createPendingResult(TASK_CODE, intent, 0);
|
val pi = createPendingResult(TASK_CODE, intent, 0)
|
||||||
intent.putExtra("COMMAND", "START")
|
|
||||||
intent.putExtra(PARAM_PINTENT, pi)
|
intent.putExtra(PARAM_PINTENT, pi)
|
||||||
|
intent.putExtra(COMMAND, START)
|
||||||
|
intent.putStringArrayListExtra(PEERS, serializePeerInfoSet2StringList(currentPeers))
|
||||||
|
intent.putStringArrayListExtra(DNS, serializeDNSInfoSet2StringList(currentDNS))
|
||||||
|
intent.putExtra(STATIC_IP, findViewById<Switch>(R.id.staticIP).isChecked)
|
||||||
|
|
||||||
startService(intent)
|
startService(intent)
|
||||||
}
|
}
|
||||||
|
if (requestCode == VPN_REQUEST_CODE && resultCode== Activity.RESULT_CANCELED){
|
||||||
|
isCancelled = true
|
||||||
|
}
|
||||||
|
if (requestCode == PEER_LIST_CODE && resultCode== Activity.RESULT_OK){
|
||||||
|
if(data!!.extras!=null){
|
||||||
|
var currentPeers = data.extras!!.getStringArrayList(PEER_LIST)
|
||||||
|
/*WiFi Direct test. need peer empty list*/
|
||||||
|
this.currentPeers = deserializeStringList2PeerInfoSet(currentPeers)
|
||||||
|
val adapter = PeerInfoListAdapter(this, this.currentPeers.sortedWith(compareBy { it.ping }))
|
||||||
|
val listView = findViewById<ListView>(R.id.peers)
|
||||||
|
listView.adapter = adapter
|
||||||
|
|
||||||
|
//save to shared preferences
|
||||||
|
val preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
|
preferences.edit().putStringSet(CURRENT_PEERS, HashSet(currentPeers)).apply()
|
||||||
|
if(isStarted){
|
||||||
|
//TODO implement UpdateConfig method in native interface and apply peer changes
|
||||||
|
stopVpn()
|
||||||
|
val i = baseContext.packageManager
|
||||||
|
.getLaunchIntentForPackage(baseContext.packageName)
|
||||||
|
i!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
i.putExtra(START_VPN, true)
|
||||||
|
startActivity(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestCode == DNS_LIST_CODE && resultCode== Activity.RESULT_OK){
|
||||||
|
if(data!!.extras!=null){
|
||||||
|
var currentDNS = data.extras!!.getStringArrayList(DNS_LIST)
|
||||||
|
if(currentDNS==null || currentDNS.size==0){
|
||||||
|
showToast("No DNS selected!")
|
||||||
|
} else {
|
||||||
|
this.currentDNS = deserializeStringList2DNSInfoSet(currentDNS)
|
||||||
|
val adapter = DNSInfoListAdapter(this, this.currentDNS.sortedWith(compareBy { it.ping }))
|
||||||
|
val listView = findViewById<ListView>(R.id.dns)
|
||||||
|
listView.adapter = adapter
|
||||||
|
//save to shared preferences
|
||||||
|
val preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
|
preferences.edit().putStringSet(CURRENT_DNS, HashSet(currentDNS)).apply()
|
||||||
|
if(isStarted){
|
||||||
|
updateDNS()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
when (resultCode) {
|
when (resultCode) {
|
||||||
STATUS_START -> print("service started")
|
STATUS_START -> {
|
||||||
|
print("service started")
|
||||||
|
if(this.currentPeers.isEmpty()){
|
||||||
|
//this is Mesh mode, send Peers update every 5 sec
|
||||||
|
thread(start = true) {
|
||||||
|
while(true) {
|
||||||
|
Thread.sleep(5000)
|
||||||
|
if(isStarted && this.currentPeers.isEmpty()) {
|
||||||
|
updatePeers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
STATUS_FINISH -> {
|
STATUS_FINISH -> {
|
||||||
val result: String = data!!.getStringExtra(IPv6)
|
isStarted = true
|
||||||
findViewById<TextView>(R.id.ip).setText(result)
|
val ipLayout = findViewById<LinearLayout>(R.id.ipLayout)
|
||||||
|
ipLayout.visibility = View.VISIBLE
|
||||||
|
address = data!!.getStringExtra(IPv6)
|
||||||
|
findViewById<TextView>(R.id.ip).text = address
|
||||||
|
}
|
||||||
|
STATUS_STOP -> {
|
||||||
|
isStarted = false
|
||||||
|
val ipLayout = findViewById<LinearLayout>(R.id.ipLayout)
|
||||||
|
ipLayout.visibility = View.GONE
|
||||||
|
}
|
||||||
|
STATUS_PEERS_UPDATE ->{
|
||||||
|
if(data!!.extras!=null) {
|
||||||
|
thread(start = true) {
|
||||||
|
val meshPeers = deserializePeerStringList2PeerInfoSet(
|
||||||
|
data.extras!!.getStringArrayList(MESH_PEERS)
|
||||||
|
)
|
||||||
|
val listView = findViewById<ListView>(R.id.peers)
|
||||||
|
val adapter = PeerInfoListAdapter(
|
||||||
|
this@MainActivity,
|
||||||
|
meshPeers.filter { it.schema!="self" }.sortedWith(compareBy { it.ping })
|
||||||
|
)
|
||||||
|
runOnUiThread {
|
||||||
|
listView.adapter = adapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> { // Note the block
|
else -> { // Note the block
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showToast(text: String){
|
||||||
|
val duration = Toast.LENGTH_SHORT
|
||||||
|
val toast = Toast.makeText(applicationContext, text, duration)
|
||||||
|
toast.setGravity(Gravity.CENTER, 0, 0)
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO reimplement it
|
||||||
|
private fun isYggServiceRunning(context: Context): Boolean {
|
||||||
|
val manager =
|
||||||
|
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||||
|
for (service in manager.getRunningServices(Int.MAX_VALUE)) {
|
||||||
|
if (YggdrasilTunService::class.java.getName() == service.service.className) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||||
|
super.onRestoreInstanceState(savedInstanceState)
|
||||||
|
val preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
|
findViewById<Switch>(R.id.staticIP).isChecked =
|
||||||
|
preferences.getString(STATIC_IP, null) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
if (meshPeersReceiver != null){
|
||||||
|
unregisterReceiver(meshPeersReceiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,249 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.*
|
||||||
|
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 com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.hbb20.CCPCountry
|
||||||
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
|
import io.github.chronosx88.yggdrasil.models.Status
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.DropDownAdapter
|
||||||
|
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 java.io.ByteArrayOutputStream
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URL
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
|
||||||
|
class PeerListActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PEER_LIST_URL = "https://publicpeers.neilalexander.dev/publicnodes.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadJson(link: String): String {
|
||||||
|
URL(link).openStream().use { input ->
|
||||||
|
var outStream = ByteArrayOutputStream()
|
||||||
|
outStream.use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
return String(outStream.toByteArray(), Charset.forName("UTF-8"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isLoading = true;
|
||||||
|
|
||||||
|
var popup: PopupWindow? = null
|
||||||
|
var adapter: DropDownAdapter? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_peer_list)
|
||||||
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
|
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view ->
|
||||||
|
addNewPeer()
|
||||||
|
}
|
||||||
|
var extras = intent.extras
|
||||||
|
var peerList = findViewById<ListView>(R.id.peerList)
|
||||||
|
var adapter = SelectPeerInfoListAdapter(this, arrayListOf(), mutableSetOf())
|
||||||
|
peerList.adapter = adapter
|
||||||
|
|
||||||
|
GlobalScope.launch {
|
||||||
|
try {
|
||||||
|
var cp = deserializeStringList2PeerInfoSet(
|
||||||
|
extras!!.getStringArrayList(MainActivity.PEER_LIST)!!
|
||||||
|
)
|
||||||
|
for(pi in cp){
|
||||||
|
var ping = ping(pi.address, pi.port)
|
||||||
|
pi.ping = ping
|
||||||
|
}
|
||||||
|
var json = downloadJson(PEER_LIST_URL)
|
||||||
|
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)
|
||||||
|
for ((country, peers) in peersMap.entries) {
|
||||||
|
for ((peer, status) in peers) {
|
||||||
|
if (status.up) {
|
||||||
|
for (ccp in countries) {
|
||||||
|
if (ccp.name.toLowerCase()
|
||||||
|
.contains(country.replace(".md", "").replace("-", " "))
|
||||||
|
) {
|
||||||
|
var url = URI(peer)
|
||||||
|
try {
|
||||||
|
var address = InetAddress.getByName(url.host)
|
||||||
|
var peerInfo =
|
||||||
|
PeerInfo(url.scheme, address, url.port, ccp.nameCode)
|
||||||
|
if(cp.contains(peerInfo)){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var ping = ping(address, url.port)
|
||||||
|
peerInfo.ping = ping
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
adapter.addItem(peerInfo)
|
||||||
|
if(adapter.count % 5 == 0) {
|
||||||
|
adapter.sort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Throwable){
|
||||||
|
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){
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
this.resources.configuration.locales[0].country
|
||||||
|
} else {
|
||||||
|
this.resources.configuration.locale.country
|
||||||
|
}
|
||||||
|
var schemaInput = view.findViewById<TextView>(R.id.schemaInput)
|
||||||
|
var ipInput = view.findViewById<TextView>(R.id.ipInput)
|
||||||
|
ipInput.requestFocus()
|
||||||
|
schemaInput.showSoftInputOnFocus = false
|
||||||
|
schemaInput.setOnFocusChangeListener { v, b ->
|
||||||
|
if(schemaInput.isFocused) {
|
||||||
|
onClickSchemaList(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schemaInput.setOnClickListener { v->
|
||||||
|
onClickSchemaList(v)
|
||||||
|
}
|
||||||
|
getPopupWindow(R.layout.spinner_item, resources.getStringArray(R.array.schemas), schemaInput, getString(R.string.schema));
|
||||||
|
var ccp = view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp)
|
||||||
|
ccp.setCountryForNameCode(countryCode)
|
||||||
|
val ab: AlertDialog.Builder = AlertDialog.Builder(this)
|
||||||
|
ab.setCancelable(true).setView(view)
|
||||||
|
var ad = ab.show()
|
||||||
|
var addButton = view.findViewById<Button>(R.id.add)
|
||||||
|
addButton.setOnClickListener{
|
||||||
|
var portInput = view.findViewById<TextView>(R.id.portInput)
|
||||||
|
var ccpInput = view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp)
|
||||||
|
var schema = schemaInput.text.toString().toLowerCase()
|
||||||
|
var ip = ipInput.text.toString().toLowerCase()
|
||||||
|
var port = portInput.text.toString().toInt()
|
||||||
|
var ccp = ccpInput.selectedCountryNameCode
|
||||||
|
GlobalScope.launch {
|
||||||
|
var pi = PeerInfo(schema, InetAddress.getByName(ip), port, ccp)
|
||||||
|
try {
|
||||||
|
var ping = ping(pi.address, pi.port)
|
||||||
|
pi.ping = ping
|
||||||
|
} catch(e: Throwable){
|
||||||
|
pi.ping = Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
var selectAdapter = (findViewById<ListView>(R.id.peerList).adapter as SelectPeerInfoListAdapter)
|
||||||
|
selectAdapter.addItem(0, pi)
|
||||||
|
selectAdapter.notifyDataSetChanged()
|
||||||
|
ad.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onClickSchemaList(v: View) {
|
||||||
|
val height = -1 * v.height +30
|
||||||
|
getAddressListPopup()?.showAsDropDown(v, -5, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAddressListPopup(): PopupWindow? {
|
||||||
|
return popup
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPopupWindow(
|
||||||
|
textViewResourceId: Int,
|
||||||
|
objects: Array<String>,
|
||||||
|
editText: TextView,
|
||||||
|
hint: String?
|
||||||
|
): PopupWindow? {
|
||||||
|
// initialize a pop up window type
|
||||||
|
val popupWindow = PopupWindow(this)
|
||||||
|
// the drop down list is a list view
|
||||||
|
val listView = ListView(this)
|
||||||
|
listView.dividerHeight = 0
|
||||||
|
// set our adapter and pass our pop up window contents
|
||||||
|
adapter = DropDownAdapter(this, textViewResourceId, objects, popupWindow, editText)
|
||||||
|
listView.adapter = adapter
|
||||||
|
// set the item click listener
|
||||||
|
listView.onItemClickListener = adapter
|
||||||
|
// some other visual settings
|
||||||
|
popupWindow.isFocusable = true
|
||||||
|
//popupWindow.setWidth(400);
|
||||||
|
val display: Display =
|
||||||
|
(this.getSystemService(Context.WINDOW_SERVICE) as WindowManager).getDefaultDisplay()
|
||||||
|
popupWindow.width = 320
|
||||||
|
popupWindow.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||||
|
// set the list view as pop up window content
|
||||||
|
popupWindow.contentView = listView
|
||||||
|
popup = popupWindow
|
||||||
|
return popupWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
val result = Intent(this, MainActivity::class.java)
|
||||||
|
var adapter = findViewById<ListView>(R.id.peerList).adapter as SelectPeerInfoListAdapter
|
||||||
|
val selectedPeers = adapter.getSelectedPeers()
|
||||||
|
/* WiFi Direct test - no peers is needed
|
||||||
|
if(selectedPeers.size>0) {
|
||||||
|
result.putExtra(MainActivity.PEER_LIST, serializePeerInfoSet2StringList(selectedPeers))
|
||||||
|
setResult(Activity.RESULT_OK, result)
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
val text = "Select at least one peer"
|
||||||
|
val duration = Toast.LENGTH_SHORT
|
||||||
|
val toast = Toast.makeText(applicationContext, text, duration)
|
||||||
|
toast.setGravity(Gravity.CENTER, 0, 0)
|
||||||
|
toast.show()
|
||||||
|
}*/
|
||||||
|
result.putExtra(MainActivity.PEER_LIST, serializePeerInfoSet2StringList(selectedPeers))
|
||||||
|
setResult(Activity.RESULT_OK, result)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,10 @@
|
|||||||
package io.github.chronosx88.yggdrasil
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build.CPU_ABI
|
|
||||||
import android.util.Log
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import config.NodeConfig
|
import config.NodeConfig
|
||||||
import io.github.chronosx88.yggdrasil.models.config.Config
|
import io.github.chronosx88.yggdrasil.models.config.Config
|
||||||
import org.hjson.JsonValue
|
|
||||||
import org.hjson.Stringify
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.Runtime.getRuntime
|
|
||||||
|
|
||||||
val gson = Gson()
|
val gson = Gson()
|
||||||
|
|
||||||
|
@ -1,107 +1,207 @@
|
|||||||
package io.github.chronosx88.yggdrasil
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.ConnectivityManager
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
|
import android.os.Build
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.system.OsConstants
|
import android.system.OsConstants
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
import dummy.ConduitEndpoint
|
import dummy.ConduitEndpoint
|
||||||
import kotlinx.coroutines.GlobalScope
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
import kotlinx.coroutines.cancel
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
import kotlinx.coroutines.launch
|
import io.github.chronosx88.yggdrasil.models.config.Peer
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.convertPeer2PeerStringList
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.convertPeerInfoSet2PeerIdSet
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2DNSInfoSet
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2PeerInfoSet
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import mobile.Mobile
|
import mobile.Mobile
|
||||||
import mobile.Yggdrasil
|
import mobile.Yggdrasil
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.nio.ByteBuffer
|
import java.net.Inet6Address
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
|
|
||||||
class YggdrasilTunService : VpnService() {
|
class YggdrasilTunService : VpnService() {
|
||||||
|
|
||||||
/** Maximum packet size is constrained by the MTU, which is given as a signed short. */
|
private lateinit var ygg: Yggdrasil
|
||||||
private val MAX_PACKET_SIZE = Short.MAX_VALUE.toInt()
|
private var isClosed = false
|
||||||
|
|
||||||
|
/** Maximum packet size is constrained by the MTU, which is given as a signed short - 256 */
|
||||||
|
private val MAX_PACKET_SIZE = 65535
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "Yggdrasil-service"
|
private const val TAG = "Yggdrasil-service"
|
||||||
}
|
}
|
||||||
private var tunInterface: ParcelFileDescriptor? = null
|
private var tunInterface: ParcelFileDescriptor? = null
|
||||||
private lateinit var yggConduitEndpoint: ConduitEndpoint
|
|
||||||
private var tunInputStream: InputStream? = null
|
private var tunInputStream: InputStream? = null
|
||||||
private var tunOutputStream: OutputStream? = null
|
private var tunOutputStream: OutputStream? = null
|
||||||
private lateinit var readCoroutine: CoroutineContext
|
private var scope: CoroutineScope? = null
|
||||||
private lateinit var writeCoroutine: CoroutineContext
|
private var address: String? = null
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
private var mNotificationManager: NotificationManager? = null
|
||||||
if (intent?.getStringExtra("COMMAND") == "STOP") {
|
|
||||||
stopVpn()
|
|
||||||
}
|
|
||||||
if (intent?.getStringExtra("COMMAND") == "START") {
|
|
||||||
val pi: PendingIntent = intent.getParcelableExtra(MainActivity.PARAM_PINTENT)
|
|
||||||
setupTunInterface(pi)
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onStartCommand(intent, flags, startId);
|
private val FOREGROUND_ID = 1338
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTunInterface(pi: PendingIntent) {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
pi.send(MainActivity.STATUS_START);
|
val pi: PendingIntent? = intent?.getParcelableExtra(MainActivity.PARAM_PINTENT)
|
||||||
val builder = Builder()
|
when(intent?.getStringExtra(MainActivity.COMMAND)){
|
||||||
val ygg = Yggdrasil()
|
MainActivity.STOP ->{
|
||||||
var configJson = Mobile.generateConfigJSON()
|
stopVpn(pi)
|
||||||
val gson = Gson()
|
startForeground(FOREGROUND_ID, foregroundNotification("Yggdrasil service stopped"))
|
||||||
|
}
|
||||||
|
MainActivity.START ->{
|
||||||
|
val peers = deserializeStringList2PeerInfoSet(intent.getStringArrayListExtra(MainActivity.PEERS))
|
||||||
|
val dns = deserializeStringList2DNSInfoSet(intent.getStringArrayListExtra(MainActivity.DNS))
|
||||||
|
val staticIP: Boolean = intent.getBooleanExtra(MainActivity.STATIC_IP, false)
|
||||||
|
ygg = Yggdrasil()
|
||||||
|
setupTunInterface(pi, peers, dns, staticIP)
|
||||||
|
startForeground(FOREGROUND_ID, foregroundNotification("Yggdrasil service started"))
|
||||||
|
}
|
||||||
|
MainActivity.UPDATE_DNS ->{
|
||||||
|
val dns = deserializeStringList2DNSInfoSet(intent.getStringArrayListExtra(MainActivity.DNS))
|
||||||
|
setupIOStreams(dns)
|
||||||
|
}
|
||||||
|
MainActivity.UPDATE_PEERS ->{
|
||||||
|
sendMeshPeerStatus(pi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var config = gson.fromJson(String(configJson), Map::class.java).toMutableMap()
|
return START_NOT_STICKY
|
||||||
config = fixConfig(config)
|
}
|
||||||
configJson = gson.toJson(config).toByteArray()
|
|
||||||
|
|
||||||
yggConduitEndpoint = ygg.startJSON(configJson)
|
private fun setupIOStreams(dns: MutableSet<DNSInfo>){
|
||||||
val address = ygg.addressString // hack for getting generic ipv6 string from NodeID
|
address = ygg.addressString
|
||||||
|
|
||||||
tunInterface = builder
|
var builder = Builder()
|
||||||
.addAddress(address, 7)
|
.addAddress(address, 7)
|
||||||
.allowFamily(OsConstants.AF_INET)
|
.allowFamily(OsConstants.AF_INET)
|
||||||
.setMtu(MAX_PACKET_SIZE)
|
.setMtu(MAX_PACKET_SIZE)
|
||||||
.establish()
|
if (dns.size > 0) {
|
||||||
|
builder.addDnsServer(address)
|
||||||
tunInputStream = FileInputStream(tunInterface!!.fileDescriptor)
|
for (d in dns) {
|
||||||
tunOutputStream = FileOutputStream(tunInterface!!.fileDescriptor)
|
builder.addDnsServer(d.address)
|
||||||
readCoroutine = GlobalScope.launch {
|
|
||||||
var buffer = ByteArray(2048)
|
|
||||||
while (true) {
|
|
||||||
try{
|
|
||||||
readPacketsFromTun(buffer)
|
|
||||||
} catch (e: IOException){
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeCoroutine = GlobalScope.launch {
|
/*
|
||||||
while (true) {
|
fix for DNS unavailability
|
||||||
writePacketsToTun()
|
*/
|
||||||
|
if(!hasIpv6DefaultRoute()){
|
||||||
|
builder.addRoute("2000::",3)
|
||||||
|
}
|
||||||
|
if(tunInterface!=null){
|
||||||
|
tunInterface!!.close()
|
||||||
|
tunInputStream!!.close()
|
||||||
|
tunOutputStream!!.close()
|
||||||
|
}
|
||||||
|
tunInterface = builder.establish()
|
||||||
|
tunInputStream = FileInputStream(tunInterface!!.fileDescriptor)
|
||||||
|
tunOutputStream = FileOutputStream(tunInterface!!.fileDescriptor)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupTunInterface(
|
||||||
|
pi: PendingIntent?,
|
||||||
|
peers: Set<PeerInfo>,
|
||||||
|
dns: MutableSet<DNSInfo>,
|
||||||
|
staticIP: Boolean
|
||||||
|
) {
|
||||||
|
pi!!.send(MainActivity.STATUS_START)
|
||||||
|
var configJson = Mobile.generateConfigJSON()
|
||||||
|
val gson = Gson()
|
||||||
|
var config = gson.fromJson(String(configJson), Map::class.java).toMutableMap()
|
||||||
|
config = fixConfig(config, peers, staticIP)
|
||||||
|
|
||||||
|
configJson = gson.toJson(config).toByteArray()
|
||||||
|
|
||||||
|
var yggConduitEndpoint = ygg.startJSON(configJson)
|
||||||
|
|
||||||
|
setupIOStreams(dns)
|
||||||
|
|
||||||
|
val job = SupervisorJob()
|
||||||
|
scope = CoroutineScope(Dispatchers.Default + job)
|
||||||
|
scope!!.launch {
|
||||||
|
val buffer = ByteArray(2048)
|
||||||
|
while (!isClosed) {
|
||||||
|
readPacketsFromTun(yggConduitEndpoint, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope!!.launch {
|
||||||
|
while (!isClosed) {
|
||||||
|
writePacketsToTun(yggConduitEndpoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val intent: Intent = Intent().putExtra(MainActivity.IPv6, address)
|
val intent: Intent = Intent().putExtra(MainActivity.IPv6, address)
|
||||||
pi.send(this, MainActivity.STATUS_FINISH, intent)
|
pi.send(this, MainActivity.STATUS_FINISH, intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fixConfig(config: MutableMap<Any?, Any?>): MutableMap<Any?, Any?> {
|
private fun sendMeshPeerStatus(pi: PendingIntent?){
|
||||||
val peers = arrayListOf<String>();
|
class Token : TypeToken<List<Peer>>()
|
||||||
peers.add("tcp://194.177.21.156:5066")
|
var add = ygg.addressString
|
||||||
peers.add("tcp://46.151.26.194:60575")
|
var meshPeers: List<Peer> = gson.fromJson(ygg.peersJSON, Token().type)
|
||||||
peers.add("tcp://188.226.125.64:54321")
|
val intent: Intent = Intent().putStringArrayListExtra(
|
||||||
|
MainActivity.MESH_PEERS,
|
||||||
|
convertPeer2PeerStringList(meshPeers)
|
||||||
|
);
|
||||||
|
pi?.send(this, MainActivity.STATUS_PEERS_UPDATE, intent)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fixConfig(
|
||||||
|
config: MutableMap<Any?, Any?>,
|
||||||
|
peers: Set<PeerInfo>,
|
||||||
|
staticIP: Boolean
|
||||||
|
): MutableMap<Any?, Any?> {
|
||||||
|
|
||||||
val whiteList = arrayListOf<String>()
|
val whiteList = arrayListOf<String>()
|
||||||
whiteList.add("")
|
whiteList.add("")
|
||||||
val blackList = arrayListOf<String>()
|
val blackList = arrayListOf<String>()
|
||||||
blackList.add("")
|
blackList.add("")
|
||||||
config["Peers"] = peers
|
config["Peers"] = convertPeerInfoSet2PeerIdSet(peers)
|
||||||
config["Listen"] = ""
|
config["Listen"] = ""
|
||||||
config["AdminListen"] = "tcp://localhost:9001"
|
config["AdminListen"] = "tcp://localhost:9001"
|
||||||
config["IfName"] = "tun0"
|
config["IfName"] = "tun0"
|
||||||
//config["EncryptionPublicKey"] = "b15633cf66e63a04f03e9d1a5b2ac6411af819cde9e74175cf574d5599b1296c"
|
if(staticIP) {
|
||||||
//config["EncryptionPrivateKey"] = "a39e2da3ccbb5afc3854574a2e3823e881d2d720754d6fdc877f57b252d3b521"
|
val preferences =
|
||||||
//config["SigningPublicKey"] = "4f248483c094aea370fba86f1630ba5099cb230aa1337ab6ef6ff0b132be2c2b"
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
//config["SigningPrivateKey"] = "e4d56eb2e15e25d9098731e39d661a80c523f31d38b71cbd0ad25a5cde745eac4f248483c094aea370fba86f1630ba5099cb230aa1337ab6ef6ff0b132be2c2b"
|
if(preferences.getString(MainActivity.STATIC_IP, null)==null) {
|
||||||
|
val encryptionPublicKey = config["EncryptionPublicKey"].toString()
|
||||||
|
val encryptionPrivateKey = config["EncryptionPrivateKey"].toString()
|
||||||
|
val signingPublicKey = config["SigningPublicKey"].toString()
|
||||||
|
val signingPrivateKey = config["SigningPrivateKey"].toString()
|
||||||
|
preferences.edit()
|
||||||
|
.putString(MainActivity.signingPrivateKey, signingPrivateKey)
|
||||||
|
.putString(MainActivity.signingPublicKey, signingPublicKey)
|
||||||
|
.putString(MainActivity.encryptionPrivateKey, encryptionPrivateKey)
|
||||||
|
.putString(MainActivity.encryptionPublicKey, encryptionPublicKey)
|
||||||
|
.putString(MainActivity.STATIC_IP,MainActivity.STATIC_IP).apply()
|
||||||
|
} else {
|
||||||
|
val signingPrivateKey = preferences.getString(MainActivity.signingPrivateKey, null)
|
||||||
|
val signingPublicKey = preferences.getString(MainActivity.signingPublicKey, null)
|
||||||
|
val encryptionPrivateKey = preferences.getString(MainActivity.encryptionPrivateKey, null)
|
||||||
|
val encryptionPublicKey = preferences.getString(MainActivity.encryptionPublicKey, null)
|
||||||
|
|
||||||
|
config["SigningPrivateKey"] = signingPrivateKey
|
||||||
|
config["SigningPublicKey"] = signingPublicKey
|
||||||
|
config["EncryptionPrivateKey"] = encryptionPrivateKey
|
||||||
|
config["EncryptionPublicKey"] = encryptionPublicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(config["SessionFirewall"] as MutableMap<Any, Any>)["Enable"] = false
|
(config["SessionFirewall"] as MutableMap<Any, Any>)["Enable"] = false
|
||||||
//(config["SessionFirewall"] as MutableMap<Any, Any>)["AllowFromDirect"] = true
|
//(config["SessionFirewall"] as MutableMap<Any, Any>)["AllowFromDirect"] = true
|
||||||
//(config["SessionFirewall"] as MutableMap<Any, Any>)["AllowFromRemote"] = true
|
//(config["SessionFirewall"] as MutableMap<Any, Any>)["AllowFromRemote"] = true
|
||||||
@ -119,37 +219,94 @@ class YggdrasilTunService : VpnService() {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readPacketsFromTun(buffer: ByteArray) {
|
private fun readPacketsFromTun(yggConduitEndpoint: ConduitEndpoint, buffer: ByteArray) {
|
||||||
if(tunInputStream != null) {
|
try {
|
||||||
// Read the outgoing packet from the input stream.
|
// Read the outgoing packet from the input stream.
|
||||||
var length = tunInputStream!!.read(buffer)
|
val length = tunInputStream?.read(buffer) ?: 1
|
||||||
if (length > 0) {
|
if (length > 0){
|
||||||
var byteBuffer = ByteBuffer.allocate(length);
|
yggConduitEndpoint.send(buffer.sliceArray(IntRange(0, length - 1)))
|
||||||
byteBuffer.put(buffer, 0, length)
|
|
||||||
yggConduitEndpoint.send(byteBuffer.array())
|
|
||||||
} else {
|
} else {
|
||||||
Thread.sleep(10)
|
Thread.sleep(100)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writePacketsToTun(yggConduitEndpoint: ConduitEndpoint) {
|
||||||
|
val buffer = yggConduitEndpoint.recv()
|
||||||
|
if(buffer!=null) {
|
||||||
|
try {
|
||||||
|
tunOutputStream?.write(buffer)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writePacketsToTun() {
|
private fun stopVpn(pi: PendingIntent?) {
|
||||||
if(tunOutputStream != null) {
|
isClosed = true;
|
||||||
val buffer = yggConduitEndpoint.recv()
|
tunInputStream!!.close()
|
||||||
tunOutputStream!!.write(buffer)
|
tunOutputStream!!.close()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopVpn() {
|
|
||||||
readCoroutine.cancel()
|
|
||||||
writeCoroutine.cancel()
|
|
||||||
tunInterface!!.close()
|
tunInterface!!.close()
|
||||||
tunInterface = null
|
tunInterface = null
|
||||||
|
scope!!.coroutineContext.cancelChildren()
|
||||||
|
Log.d(TAG,"Stop is running from service")
|
||||||
|
ygg.stop()
|
||||||
|
val intent: Intent = Intent()
|
||||||
|
pi!!.send(this, MainActivity.STATUS_STOP, intent)
|
||||||
|
stopForeground(true)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
stopForeground(true)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun hasIpv6DefaultRoute(): Boolean {
|
||||||
|
val cm =
|
||||||
|
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
val networks = cm.allNetworks
|
||||||
|
for (network in networks) {
|
||||||
|
val linkProperties = cm.getLinkProperties(network)
|
||||||
|
val routes = linkProperties.routes
|
||||||
|
for (route in routes) {
|
||||||
|
if (route.isDefaultRoute && route.gateway is Inet6Address) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun foregroundNotification(text: String): Notification? {
|
||||||
|
val channelId =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
createNotificationChannel(TAG, "Yggdrasil service")
|
||||||
|
} else {
|
||||||
|
// If earlier version channel ID is not used
|
||||||
|
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||||
|
""
|
||||||
|
}
|
||||||
|
val b = NotificationCompat.Builder(this, channelId)
|
||||||
|
b.setOngoing(true)
|
||||||
|
.setContentTitle(getString(R.string.app_name))
|
||||||
|
.setContentText(text)
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.setTicker(text)
|
||||||
|
return b.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createNotificationChannel(channelId: String, channelName: String): String{
|
||||||
|
val chan = NotificationChannel(channelId,
|
||||||
|
channelName, NotificationManager.IMPORTANCE_NONE)
|
||||||
|
chan.lightColor = getColor(R.color.dark_10)
|
||||||
|
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||||
|
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
service.createNotificationChannel(chan)
|
||||||
|
return channelId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.hbb20.CCPCountry
|
||||||
|
import com.hbb20.CountryCodePicker
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
|
||||||
|
class DNSInfo {
|
||||||
|
|
||||||
|
constructor(address: InetAddress, countryCode: String, description: String){
|
||||||
|
this.address = address
|
||||||
|
this.countryCode = countryCode
|
||||||
|
this.description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
var address: InetAddress
|
||||||
|
var countryCode: String
|
||||||
|
var description: String
|
||||||
|
var ping: Int = Int.MAX_VALUE
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "[" + address.toString().substring(1) + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return toString() == other.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCountry(context: Context): CCPCountry? {
|
||||||
|
return CCPCountry.getCountryForNameCodeFromLibraryMasterList(context, CountryCodePicker.Language.ENGLISH, countryCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.hbb20.CCPCountry
|
||||||
|
import com.hbb20.CountryCodePicker
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
|
||||||
|
class PeerInfo {
|
||||||
|
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]
|
||||||
|
} else {
|
||||||
|
this.hostName = a.substring(1)
|
||||||
|
}
|
||||||
|
this.port = port
|
||||||
|
this.countryCode = countryCode
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
} else {
|
||||||
|
this.hostName = a.substring(1)
|
||||||
|
}
|
||||||
|
this.port = port
|
||||||
|
this.countryCode = countryCode
|
||||||
|
this.isMeshPeer = isMeshPeer
|
||||||
|
}
|
||||||
|
|
||||||
|
var schema: String
|
||||||
|
var address: InetAddress
|
||||||
|
var hostName: String
|
||||||
|
var port = 0
|
||||||
|
var countryCode: String?=null
|
||||||
|
var ping: Int = Int.MAX_VALUE
|
||||||
|
var isMeshPeer = false
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
if(this.hostName.contains(":")) {
|
||||||
|
return this.schema + "://[" + this.hostName + "]:" + port
|
||||||
|
} else {
|
||||||
|
return this.schema + "://" + this.hostName + ":" + port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return toString() == other.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCountry(context: Context): CCPCountry? {
|
||||||
|
if(countryCode==null){
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return CCPCountry.getCountryForNameCodeFromLibraryMasterList(
|
||||||
|
context,
|
||||||
|
CountryCodePicker.Language.ENGLISH,
|
||||||
|
countryCode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models
|
||||||
|
|
||||||
|
class Status {
|
||||||
|
var up: Boolean = false
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import io.github.chronosx88.yggdrasil.R
|
||||||
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
|
|
||||||
|
|
||||||
|
class DNSInfoListAdapter(
|
||||||
|
context: Context,
|
||||||
|
allDNS: List<DNSInfo>
|
||||||
|
) : ArrayAdapter<DNSInfo?> (context, 0, allDNS) {
|
||||||
|
|
||||||
|
private val mContext: Context = context
|
||||||
|
private var allDNS: List<DNSInfo> = allDNS
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var dnsInfoHolder = DNSInfoHolder()
|
||||||
|
var listItem: View? = convertView
|
||||||
|
if (listItem == null) {
|
||||||
|
listItem = LayoutInflater.from(mContext).inflate(R.layout.host_list_item, parent, false)
|
||||||
|
dnsInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
||||||
|
dnsInfoHolder.dnsInfoText = listItem.findViewById(R.id.hostInfoText) as TextView
|
||||||
|
listItem.tag = dnsInfoHolder
|
||||||
|
} else {
|
||||||
|
dnsInfoHolder = listItem.tag as DNSInfoHolder
|
||||||
|
}
|
||||||
|
val currentDNS = allDNS[position]
|
||||||
|
dnsInfoHolder.countryFlag.setImageResource(currentDNS.getCountry(mContext)!!.flagID)
|
||||||
|
dnsInfoHolder.dnsInfoText.text = currentDNS.toString()
|
||||||
|
return listItem!!
|
||||||
|
}
|
||||||
|
|
||||||
|
class DNSInfoHolder {
|
||||||
|
lateinit var countryFlag: ImageView
|
||||||
|
lateinit var dnsInfoText: TextView
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnimationUtils
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.PopupWindow
|
||||||
|
import android.widget.TextView
|
||||||
|
import io.github.chronosx88.yggdrasil.R
|
||||||
|
|
||||||
|
|
||||||
|
class DropDownAdapter(
|
||||||
|
context: Context,
|
||||||
|
textViewResourceId: Int,
|
||||||
|
objects: Array<String>,
|
||||||
|
popup: PopupWindow,
|
||||||
|
editText: TextView
|
||||||
|
) :
|
||||||
|
ArrayAdapter<String?>(context, textViewResourceId, objects), OnItemClickListener {
|
||||||
|
|
||||||
|
private val objects: Array<String>
|
||||||
|
private val popup: PopupWindow
|
||||||
|
private val editText: TextView
|
||||||
|
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View? {
|
||||||
|
return getCustomView(position, convertView, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
return getCustomView(position, convertView, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCustomView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||||
|
var convertView: View? = convertView
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView =
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.dropdown_item, parent, false)
|
||||||
|
}
|
||||||
|
val sub = convertView?.findViewById(R.id.sub) as TextView
|
||||||
|
val address = objects[position]
|
||||||
|
sub.text = address
|
||||||
|
return convertView!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(arg0: AdapterView<*>?, v: View, arg2: Int, arg3: Long) {
|
||||||
|
|
||||||
|
// get the context and main activity to access variables
|
||||||
|
// add some animation when a list item was clicked
|
||||||
|
val fadeInAnimation: Animation =
|
||||||
|
AnimationUtils.loadAnimation(context, android.R.anim.fade_in)
|
||||||
|
fadeInAnimation.duration = 10
|
||||||
|
v.startAnimation(fadeInAnimation)
|
||||||
|
val text: View = v.findViewById(R.id.sub) ?: return
|
||||||
|
val address = (text as TextView).text.toString()
|
||||||
|
// dismiss the pop up
|
||||||
|
popup.dismiss()
|
||||||
|
editText.text = address
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.objects = objects
|
||||||
|
this.popup = popup
|
||||||
|
this.editText = editText
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class Peer (
|
||||||
|
//Example [{"PublicKey":[154,201,118,156,19,74,134,115,94,159,76,86,36,192,221,105,220,254,226,161,108,226,17,192,75,243,225,15,42,195,155,2],"Endpoint":"(self)","BytesSent":0,"BytesRecvd":0,"Protocol":"self","Port":0,"Uptime":209900460}]
|
||||||
|
@SerializedName("Endpoint") var endpoint : String,
|
||||||
|
@SerializedName("Port") var port : Int,
|
||||||
|
@SerializedName("Uptime") var uptime : Long,
|
||||||
|
@SerializedName("Protocol") var protocol : String,
|
||||||
|
@SerializedName("BytesSent") var bytesSent : Long,
|
||||||
|
@SerializedName("BytesRecvd") var bytesReceived : Long
|
||||||
|
)
|
@ -0,0 +1,48 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import io.github.chronosx88.yggdrasil.R
|
||||||
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
|
|
||||||
|
|
||||||
|
class PeerInfoListAdapter(
|
||||||
|
context: Context,
|
||||||
|
allPeers: List<PeerInfo>
|
||||||
|
) : ArrayAdapter<PeerInfo?> (context, 0, allPeers) {
|
||||||
|
|
||||||
|
private val mContext: Context = context
|
||||||
|
private var allPeers: List<PeerInfo> = allPeers
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var peerInfoHolder = PeerInfoHolder()
|
||||||
|
var listItem: View? = convertView
|
||||||
|
if (listItem == null) {
|
||||||
|
listItem = LayoutInflater.from(mContext).inflate(R.layout.host_list_item, parent, false)
|
||||||
|
peerInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
||||||
|
peerInfoHolder.peerInfoText = listItem.findViewById(R.id.hostInfoText) as TextView
|
||||||
|
listItem.tag = peerInfoHolder
|
||||||
|
} else {
|
||||||
|
peerInfoHolder = listItem.tag as PeerInfoHolder
|
||||||
|
}
|
||||||
|
val currentPeer = allPeers[position]
|
||||||
|
if(currentPeer.isMeshPeer){
|
||||||
|
//TODO set mesh icon
|
||||||
|
} else {
|
||||||
|
peerInfoHolder.countryFlag.setImageResource(currentPeer.getCountry(mContext)!!.flagID)
|
||||||
|
}
|
||||||
|
peerInfoHolder.peerInfoText.text = currentPeer.toString()
|
||||||
|
return listItem!!
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeerInfoHolder {
|
||||||
|
lateinit var countryFlag: ImageView
|
||||||
|
lateinit var peerInfoText: TextView
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
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 io.github.chronosx88.yggdrasil.R
|
||||||
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
|
|
||||||
|
class SelectDNSInfoListAdapter(
|
||||||
|
context: Context,
|
||||||
|
allDNS: List<DNSInfo>,
|
||||||
|
currentDNS: MutableSet<DNSInfo>
|
||||||
|
) : ArrayAdapter<DNSInfo?> (context, 0, allDNS) {
|
||||||
|
|
||||||
|
private val mContext: Context = context
|
||||||
|
private var allDNS: MutableList<DNSInfo> = allDNS as MutableList<DNSInfo>
|
||||||
|
private var currentDNS: MutableSet<DNSInfo> = currentDNS
|
||||||
|
|
||||||
|
override fun getItem(position: Int): DNSInfo? {
|
||||||
|
return allDNS[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return allDNS.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var dnsInfoHolder = DNSInfoHolder()
|
||||||
|
var listItem: View? = convertView
|
||||||
|
if (listItem == null) {
|
||||||
|
listItem = LayoutInflater.from(mContext).inflate(R.layout.host_list_item_edit, parent, false)
|
||||||
|
dnsInfoHolder.checkbox = listItem.findViewById(R.id.checkbox) as CheckBox
|
||||||
|
dnsInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
||||||
|
dnsInfoHolder.dnsInfoText = listItem.findViewById(R.id.hostInfoText) as TextView
|
||||||
|
dnsInfoHolder.ping = listItem.findViewById(R.id.ping) as TextView
|
||||||
|
listItem.tag = dnsInfoHolder
|
||||||
|
} else {
|
||||||
|
dnsInfoHolder = listItem.tag as DNSInfoHolder
|
||||||
|
}
|
||||||
|
val currentDNS = allDNS[position]
|
||||||
|
dnsInfoHolder.countryFlag.setImageResource(currentDNS.getCountry(mContext)!!.flagID)
|
||||||
|
val dnsId = currentDNS.toString()
|
||||||
|
if(currentDNS.ping == Int.MAX_VALUE){
|
||||||
|
dnsInfoHolder.dnsInfoText.text = dnsId
|
||||||
|
dnsInfoHolder.ping.text=""
|
||||||
|
dnsInfoHolder.dnsInfoText.setTextColor(Color.GRAY)
|
||||||
|
} else {
|
||||||
|
dnsInfoHolder.dnsInfoText.text = dnsId
|
||||||
|
dnsInfoHolder.ping.text = currentDNS.ping.toString() + " ms"
|
||||||
|
dnsInfoHolder.dnsInfoText.setTextColor(Color.WHITE)
|
||||||
|
}
|
||||||
|
dnsInfoHolder.checkbox.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if(isChecked){
|
||||||
|
if(!this.currentDNS.contains(currentDNS)){
|
||||||
|
this.currentDNS.add(currentDNS)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(this.currentDNS.contains(currentDNS)){
|
||||||
|
this.currentDNS.remove(currentDNS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dnsInfoHolder.checkbox.isChecked = this.currentDNS.contains(currentDNS)
|
||||||
|
return listItem!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelectedDNS(): Set<DNSInfo> {
|
||||||
|
return currentDNS
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addItem(peerInfo: DNSInfo){
|
||||||
|
allDNS.add(peerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addItem(index: Int, peerInfo: DNSInfo){
|
||||||
|
allDNS.add(index, peerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sort(){
|
||||||
|
allDNS = ArrayList(allDNS.sortedWith(compareBy { it.ping }))
|
||||||
|
this.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DNSInfoHolder {
|
||||||
|
lateinit var checkbox: CheckBox
|
||||||
|
lateinit var countryFlag: ImageView
|
||||||
|
lateinit var dnsInfoText: TextView
|
||||||
|
lateinit var ping: TextView
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
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 io.github.chronosx88.yggdrasil.R
|
||||||
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
|
|
||||||
|
class SelectPeerInfoListAdapter(
|
||||||
|
context: Context,
|
||||||
|
allPeers: List<PeerInfo>,
|
||||||
|
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
|
||||||
|
|
||||||
|
override fun getItem(position: Int): PeerInfo? {
|
||||||
|
return allPeers.get(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return allPeers.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var peerInfoHolder = PeerInfoHolder()
|
||||||
|
var listItem: View? = convertView
|
||||||
|
if (listItem == null) {
|
||||||
|
listItem = LayoutInflater.from(mContext).inflate(R.layout.host_list_item_edit, parent, false)
|
||||||
|
peerInfoHolder.checkbox = listItem.findViewById(R.id.checkbox) as CheckBox
|
||||||
|
peerInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
||||||
|
peerInfoHolder.peerInfoText = listItem.findViewById(R.id.hostInfoText) as TextView
|
||||||
|
peerInfoHolder.ping = listItem.findViewById(R.id.ping) as TextView
|
||||||
|
listItem.tag = peerInfoHolder
|
||||||
|
} else {
|
||||||
|
peerInfoHolder = listItem.tag as PeerInfoHolder
|
||||||
|
}
|
||||||
|
val currentPeer = allPeers[position]
|
||||||
|
peerInfoHolder.countryFlag.setImageResource(currentPeer.getCountry(mContext)!!.flagID)
|
||||||
|
val peerId = currentPeer.toString()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
peerInfoHolder.checkbox.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if(!isLoading) {
|
||||||
|
if (isChecked) {
|
||||||
|
if (!currentPeers.contains(currentPeer)) {
|
||||||
|
currentPeers.add(currentPeer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentPeers.contains(currentPeer)) {
|
||||||
|
currentPeers.remove(currentPeer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peerInfoHolder.checkbox.isChecked = this.currentPeers.contains(currentPeer)
|
||||||
|
return listItem!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelectedPeers(): Set<PeerInfo> {
|
||||||
|
return currentPeers
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addItem(peerInfo: PeerInfo){
|
||||||
|
allPeers.add(peerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addItem(index: Int, peerInfo: PeerInfo){
|
||||||
|
allPeers.add(index, peerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAll(index: Int, peerInfo: ArrayList<PeerInfo>){
|
||||||
|
currentPeers.addAll(peerInfo)
|
||||||
|
allPeers.removeAll(peerInfo)
|
||||||
|
allPeers.addAll(index, peerInfo)
|
||||||
|
this.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sort(){
|
||||||
|
allPeers = ArrayList(allPeers.sortedWith(compareBy { it.ping }))
|
||||||
|
this.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLoading(loading: Boolean){
|
||||||
|
this.isLoading = loading
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeerInfoHolder {
|
||||||
|
lateinit var checkbox: CheckBox
|
||||||
|
lateinit var countryFlag: ImageView
|
||||||
|
lateinit var peerInfoText: TextView
|
||||||
|
lateinit var ping: TextView
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Socket
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun deserializeStringList2PeerInfoSet(list: List<String>?): MutableSet<PeerInfo> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = mutableSetOf<PeerInfo>()
|
||||||
|
if (list != null) {
|
||||||
|
for(s in list) {
|
||||||
|
out.add(gson.fromJson(s, PeerInfo::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun deserializeStringList2DNSInfoSet(list: List<String>?): MutableSet<DNSInfo> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = mutableSetOf<DNSInfo>()
|
||||||
|
if (list != null) {
|
||||||
|
for(s in list) {
|
||||||
|
out.add(gson.fromJson(s, DNSInfo::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun deserializeStringSet2PeerInfoSet(list: Set<String>): MutableSet<PeerInfo> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = mutableSetOf<PeerInfo>()
|
||||||
|
for(s in list) {
|
||||||
|
out.add(gson.fromJson(s, PeerInfo::class.java))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun deserializeStringSet2DNSInfoSet(list: Set<String>): MutableSet<DNSInfo> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = mutableSetOf<DNSInfo>()
|
||||||
|
for(s in list) {
|
||||||
|
out.add(gson.fromJson(s, DNSInfo::class.java))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun serializePeerInfoSet2StringList(list: Set<PeerInfo>): ArrayList<String> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = ArrayList<String>()
|
||||||
|
for(p in list) {
|
||||||
|
out.add(gson.toJson(p))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun serializeDNSInfoSet2StringList(list: Set<DNSInfo>): ArrayList<String> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = ArrayList<String>()
|
||||||
|
for(p in list) {
|
||||||
|
out.add(gson.toJson(p))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun ping(address: InetAddress, port:Int): Int {
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
val socket = Socket()
|
||||||
|
try {
|
||||||
|
socket.connect(InetSocketAddress(address, port), 5000)
|
||||||
|
socket.close()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
print(address)
|
||||||
|
return Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
return (System.currentTimeMillis() - start).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun convertPeerInfoSet2PeerIdSet(list: Set<PeerInfo>): Set<String> {
|
||||||
|
var out = mutableSetOf<String>()
|
||||||
|
for(p in list) {
|
||||||
|
out.add(p.toString())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun convertPeer2PeerStringList(list: List<Peer>): ArrayList<String> {
|
||||||
|
var out = ArrayList<String>()
|
||||||
|
var gson = Gson()
|
||||||
|
for(p in list) {
|
||||||
|
out.add(gson.toJson(p))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun deserializePeerStringList2PeerInfoSet(list: List<String>?): MutableSet<PeerInfo> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = mutableSetOf<PeerInfo>()
|
||||||
|
if (list != null) {
|
||||||
|
for(s in list) {
|
||||||
|
var p = gson.fromJson(s, Peer::class.java)
|
||||||
|
if(p.endpoint == "(self)"){
|
||||||
|
out.add(PeerInfo(p.protocol, InetAddress.getByName("localhost"), p.port, null, true))
|
||||||
|
} else {
|
||||||
|
var fixWlanPart = p.endpoint.substring(p.endpoint.indexOf('%'), p.endpoint.indexOf(']'))
|
||||||
|
var fixedUrlString = p.endpoint.replace(fixWlanPart, "")
|
||||||
|
var url = URI(fixedUrlString)
|
||||||
|
out.add(
|
||||||
|
PeerInfo(
|
||||||
|
url.scheme,
|
||||||
|
InetAddress.getByName(url.host),
|
||||||
|
url.port,
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
app/src/main/res/drawable/add_peer_panel.xml
Normal file
16
app/src/main/res/drawable/add_peer_panel.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/grey" />
|
||||||
|
|
||||||
|
<solid android:color="@color/dark_30" />
|
||||||
|
|
||||||
|
<padding
|
||||||
|
android:left="1dp"
|
||||||
|
android:right="1dp"
|
||||||
|
android:bottom="1dp"
|
||||||
|
android:top="1dp" />
|
||||||
|
|
||||||
|
<corners android:radius="15dp" />
|
||||||
|
</shape>
|
15
app/src/main/res/drawable/button_background.xml
Normal file
15
app/src/main/res/drawable/button_background.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/green" />
|
||||||
|
<solid android:color="@color/green" />
|
||||||
|
|
||||||
|
<padding
|
||||||
|
android:left="1dp"
|
||||||
|
android:right="1dp"
|
||||||
|
android:bottom="1dp"
|
||||||
|
android:top="1dp" />
|
||||||
|
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
6
app/src/main/res/drawable/button_selector.xml
Normal file
6
app/src/main/res/drawable/button_selector.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/button_background" android:state_focused="true" />
|
||||||
|
<item android:drawable="@drawable/button_background" android:state_pressed="true" />
|
||||||
|
<item android:drawable="@drawable/button_background" />
|
||||||
|
</selector>
|
16
app/src/main/res/drawable/checkbox_rounded_corner.xml
Normal file
16
app/src/main/res/drawable/checkbox_rounded_corner.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="@android:color/transparent" />
|
||||||
|
|
||||||
|
<solid android:color="@color/white" />
|
||||||
|
|
||||||
|
<padding
|
||||||
|
android:left="1dp"
|
||||||
|
android:right="1dp"
|
||||||
|
android:bottom="1dp"
|
||||||
|
android:top="1dp" />
|
||||||
|
|
||||||
|
<corners android:radius="3dp" />
|
||||||
|
</shape>
|
16
app/src/main/res/drawable/edit_text_rounded_corner.xml
Normal file
16
app/src/main/res/drawable/edit_text_rounded_corner.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/grey" />
|
||||||
|
|
||||||
|
<solid android:color="@color/dark_10" />
|
||||||
|
|
||||||
|
<padding
|
||||||
|
android:left="1dp"
|
||||||
|
android:right="1dp"
|
||||||
|
android:bottom="1dp"
|
||||||
|
android:top="1dp" />
|
||||||
|
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
16
app/src/main/res/drawable/info_panel_rounded_corner.xml
Normal file
16
app/src/main/res/drawable/info_panel_rounded_corner.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/grey" />
|
||||||
|
|
||||||
|
<solid android:color="@color/dark_10" />
|
||||||
|
|
||||||
|
<padding
|
||||||
|
android:left="1dp"
|
||||||
|
android:right="1dp"
|
||||||
|
android:bottom="1dp"
|
||||||
|
android:top="1dp" />
|
||||||
|
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
40
app/src/main/res/layout/activity_dns_list.xml
Normal file
40
app/src/main/res/layout/activity_dns_list.xml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?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=".DNSListActivity"
|
||||||
|
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_dns_list" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|right"
|
||||||
|
android:layout_margin="@dimen/fab_margin"
|
||||||
|
app:backgroundTint="@color/green"
|
||||||
|
app:tint="@color/white"
|
||||||
|
app:borderWidth="0dp"
|
||||||
|
app:elevation="6dp"
|
||||||
|
app:fabSize="normal"
|
||||||
|
app:srcCompat="@android:drawable/ic_input_add"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -1,83 +1,62 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/grey"
|
tools:context=".MainActivity"
|
||||||
tools:context=".MainActivity">
|
android:background="@color/grey">
|
||||||
<LinearLayout
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appBarLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/connectRadioGroup"
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
android:orientation="vertical"
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:gravity="left">
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/ipLabel"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="40dp"
|
|
||||||
android:background="@color/dark_10"
|
|
||||||
android:elevation="8dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:text="Your IP address:"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:paddingLeft="20dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/ip"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="30dp"
|
|
||||||
android:background="@color/dark_10"
|
|
||||||
android:elevation="8dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:text=""
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:paddingLeft="20dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/connectRadioGroup"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<RadioGroup
|
|
||||||
android:id="@+id/connectRadioGroup"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="60dp"
|
|
||||||
android:background="@drawable/out_line"
|
|
||||||
android:checkedButton="@+id/offer"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:layout_editor_absoluteX="50dp">
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/disconnectButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginLeft="1dp"
|
android:orientation="horizontal">
|
||||||
android:layout_marginTop="1dp"
|
|
||||||
android:layout_marginBottom="1dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="@drawable/toggle_widget_background"
|
|
||||||
android:button="@null"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/disconnect_button"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:checked="true"/>
|
|
||||||
|
|
||||||
<RadioButton
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/connectButton"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:layout_marginTop="1dp"
|
android:layout_marginEnd="240dp"
|
||||||
android:layout_marginRight="1dp"
|
android:background="?attr/colorPrimary"
|
||||||
android:layout_marginBottom="1dp"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_weight="1"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:background="@drawable/toggle_widget_background"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:button="@null"
|
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/connect_button"
|
|
||||||
android:textColor="@color/white" />
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<Switch
|
||||||
|
android:id="@+id/staticIP"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:theme="@style/SwitchTheme"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/switchOn"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:text="Static IP"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/switchOn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:theme="@style/SwitchTheme"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:text="Ygg me!"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<include layout="@layout/content_main" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
39
app/src/main/res/layout/activity_peer_list.xml
Normal file
39
app/src/main/res/layout/activity_peer_list.xml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?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=".PeerListActivity"
|
||||||
|
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_peer_list" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|right"
|
||||||
|
android:layout_margin="@dimen/fab_margin"
|
||||||
|
app:backgroundTint="@color/green"
|
||||||
|
app:tint="@color/white"
|
||||||
|
app:borderWidth="0dp"
|
||||||
|
app:elevation="6dp"
|
||||||
|
app:fabSize="normal"
|
||||||
|
app:srcCompat="@android:drawable/ic_input_add"/>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
13
app/src/main/res/layout/content_dns_list.xml
Normal file
13
app/src/main/res/layout/content_dns_list.xml
Normal 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/dnsList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:divider="@color/dark_20"
|
||||||
|
android:dividerHeight="2px"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
156
app/src/main/res/layout/content_main.xml
Normal file
156
app/src/main/res/layout/content_main.xml
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
android:background="@color/grey">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ipLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:background="@drawable/info_panel_rounded_corner"
|
||||||
|
android:gravity="left"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
android:visibility="gone">
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/ipLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:elevation="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="IP address:"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:textColor="@color/dark_30"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/copyIp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:text="COPY"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:background="@android:color/transparent"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/ip"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:elevation="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/copyIp"
|
||||||
|
android:text=""
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/peerLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:background="@drawable/info_panel_rounded_corner"
|
||||||
|
android:gravity="left"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/ipLayout">
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/ipPeers"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:elevation="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="Peers:"
|
||||||
|
android:textColor="@color/dark_30"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
<Button
|
||||||
|
android:id="@+id/edit"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:text="EDIT"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:background="@android:color/transparent"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/peers"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:dividerHeight="0dp"
|
||||||
|
android:divider="@null"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:background="@drawable/info_panel_rounded_corner"
|
||||||
|
android:gravity="left"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/peerLayout">
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dnsLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:elevation="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="DNS:"
|
||||||
|
android:textColor="@color/dark_30"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
<Button
|
||||||
|
android:id="@+id/editDNS"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:text="EDIT"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:background="@android:color/transparent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/dns"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:dividerHeight="0dp"
|
||||||
|
android:divider="@null"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
13
app/src/main/res/layout/content_peer_list.xml
Normal file
13
app/src/main/res/layout/content_peer_list.xml
Normal 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/peerList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:divider="@color/dark_20"
|
||||||
|
android:dividerHeight="2px"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
30
app/src/main/res/layout/dropdown_item.xml
Normal file
30
app/src/main/res/layout/dropdown_item.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?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="wrap_content"
|
||||||
|
android:paddingLeft="15dp"
|
||||||
|
android:paddingRight="15dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:background="@color/dark_10">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sub"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="14dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Schema"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textIsSelectable="false"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
26
app/src/main/res/layout/host_list_item.xml
Normal file
26
app/src/main/res/layout/host_list_item.xml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="2dp">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/countryFlag"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="fitStart"/>
|
||||||
|
</FrameLayout>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/hostInfoText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingEnd="20dp"
|
||||||
|
android:minHeight="22dp"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
</LinearLayout>
|
64
app/src/main/res/layout/host_list_item_edit.xml
Normal file
64
app/src/main/res/layout/host_list_item_edit.xml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/countryFlag"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/hostInfoText"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/hostInfoText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="45dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/ping"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/countryFlag"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/ping"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="45dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/frame"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/frame"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/checkbox_rounded_corner"
|
||||||
|
android:scaleX="0.4"
|
||||||
|
android:scaleY="0.4"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:checked="false"
|
||||||
|
android:scaleX="3"
|
||||||
|
android:scaleY="3"
|
||||||
|
android:theme="@style/SwitchTheme" />
|
||||||
|
</FrameLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
13
app/src/main/res/layout/menu_save.xml
Normal file
13
app/src/main/res/layout/menu_save.xml
Normal 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/saveButton"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:text="SAVE"
|
||||||
|
android:background="@android:color/transparent"/>
|
||||||
|
</RelativeLayout>
|
7
app/src/main/res/layout/menu_switch.xml
Normal file
7
app/src/main/res/layout/menu_switch.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
</RelativeLayout>
|
63
app/src/main/res/layout/new_dns_dialog.xml
Normal file
63
app/src/main/res/layout/new_dns_dialog.xml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<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/ip"
|
||||||
|
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/ipInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:digits="0,1,2,3,4,5,6,7,8,9,:"
|
||||||
|
android:hint="IPv6"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textCursorDrawable="@null"
|
||||||
|
/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.hbb20.CountryCodePicker
|
||||||
|
android:id="@+id/ccp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
app:ccp_contentColor="@color/white"
|
||||||
|
app:ccp_showFullName="true"
|
||||||
|
app:ccp_showPhoneCode="false"
|
||||||
|
app:ccp_showNameCode="false"
|
||||||
|
app:ccpDialog_backgroundColor="@color/grey"
|
||||||
|
app:ccpDialog_textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/ip"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ip"
|
||||||
|
android:background="@drawable/edit_text_rounded_corner"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/add"
|
||||||
|
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_constraintEnd_toEndOf="@+id/schema"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ccp"
|
||||||
|
android:background="@drawable/button_selector"
|
||||||
|
app:backgroundTint="@null"
|
||||||
|
android:text="ADD"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
107
app/src/main/res/layout/new_peer_dialog.xml
Normal file
107
app/src/main/res/layout/new_peer_dialog.xml
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<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/schema"
|
||||||
|
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/schemaInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Schema"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:textCursorDrawable="@null"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/ip"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/schema"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/schema"
|
||||||
|
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/ipInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:digits="0,1,2,3,4,5,6,7,8,9,.,qwertzuiopasdfghjklyxcvbnm,_,-"
|
||||||
|
android:hint="IP/domain"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textCursorDrawable="@null"
|
||||||
|
android:focusedByDefault="true"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/port"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/schema"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ip"
|
||||||
|
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/portInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:digits="0123456789"
|
||||||
|
android:hint="Port"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:inputType="number"
|
||||||
|
android:textCursorDrawable="@null"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.hbb20.CountryCodePicker
|
||||||
|
android:id="@+id/ccp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
app:ccp_contentColor="@color/white"
|
||||||
|
app:ccp_showFullName="true"
|
||||||
|
app:ccp_showPhoneCode="false"
|
||||||
|
app:ccp_showNameCode="false"
|
||||||
|
app:ccpDialog_backgroundColor="@color/grey"
|
||||||
|
app:ccpDialog_textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/schema"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/port"
|
||||||
|
android:background="@drawable/edit_text_rounded_corner"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/add"
|
||||||
|
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_constraintEnd_toEndOf="@+id/schema"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ccp"
|
||||||
|
android:background="@drawable/button_selector"
|
||||||
|
app:backgroundTint="@null"
|
||||||
|
android:text="ADD"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
14
app/src/main/res/layout/spinner_item.xml
Normal file
14
app/src/main/res/layout/spinner_item.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingRight="20dp"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:paddingBottom="15dp"
|
||||||
|
android:textSize="20dp"
|
||||||
|
android:gravity="left"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textIsSelectable="false"/>
|
9
app/src/main/res/menu/main_menu.xml
Normal file
9
app/src/main/res/menu/main_menu.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?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/switchId"
|
||||||
|
android:title=""
|
||||||
|
app:actionLayout="@layout/menu_switch"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
</menu>
|
8
app/src/main/res/menu/save.xml
Normal file
8
app/src/main/res/menu/save.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?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/saveItem"
|
||||||
|
android:title=""
|
||||||
|
app:actionLayout="@layout/menu_save"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
</menu>
|
7
app/src/main/res/values/arrays.xml
Normal file
7
app/src/main/res/values/arrays.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string-array name="schemas">
|
||||||
|
<item>TCP</item>
|
||||||
|
<item>TLS</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
@ -9,4 +9,5 @@
|
|||||||
<color name="dark_5">#555555</color>
|
<color name="dark_5">#555555</color>
|
||||||
<color name="dark_10">#666666</color>
|
<color name="dark_10">#666666</color>
|
||||||
<color name="dark_20">#777777</color>
|
<color name="dark_20">#777777</color>
|
||||||
|
<color name="dark_30">#acacac</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
3
app/src/main/res/values/dimens.xml
Normal file
3
app/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<dimen name="fab_margin">16dp</dimen>
|
||||||
|
</resources>
|
@ -2,4 +2,9 @@
|
|||||||
<string name="app_name">Yggdrasil</string>
|
<string name="app_name">Yggdrasil</string>
|
||||||
<string name="connect_button">Connect</string>
|
<string name="connect_button">Connect</string>
|
||||||
<string name="disconnect_button">Disconnect</string>
|
<string name="disconnect_button">Disconnect</string>
|
||||||
|
<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="schema">Schema</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -6,6 +6,33 @@
|
|||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
<item name="colorControlActivated">@color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="SwitchTheme" parent="Theme.AppCompat.Light">
|
||||||
|
<item name="android:colorControlActivated">@color/green</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="EditText.OutlinedBox" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
<item name="android:textColorHint"> @color/white </item>
|
||||||
|
<item name="hintTextColor"> @color/white </item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="EditTextTheme" parent="@android:style/TextAppearance">
|
||||||
|
<item name="android:textColor">@color/white</item>
|
||||||
|
<item name="android:textColorHint">@color/white</item>
|
||||||
|
<item name="colorAccent">@color/white</item>
|
||||||
|
<item name="android:textSize">13sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBar">
|
||||||
|
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||||
|
|
||||||
|
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -5,7 +5,6 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||||
@ -19,7 +18,6 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,2 +1 @@
|
|||||||
include ':yggdrasil.aar'
|
|
||||||
include ':app', ':yggdrasil'
|
include ':app', ':yggdrasil'
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
configurations.maybeCreate("default")
|
|
||||||
artifacts.add("default", file('yggdrasil.aar'))
|
|
Binary file not shown.
@ -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/yggdrasil-network/yggdrasil-extras; \
|
go get -u github.com/vikulin/yggdrasil-extras@ab56805; \
|
||||||
ANDROID=true ./build;
|
ANDROID=true ./build;
|
||||||
mv $(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 $(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;
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
configurations.maybeCreate("default")
|
configurations.maybeCreate("default")
|
||||||
|
|
||||||
artifacts.add("default", file('yggdrasil.aar'))
|
artifacts.add("default", file('yggdrasil.aar'))
|
Loading…
x
Reference in New Issue
Block a user