@ -6,7 +6,7 @@ android {
|
|||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.github.chronosx88.yggdrasil"
|
applicationId "io.github.chronosx88.yggdrasil"
|
||||||
minSdkVersion 21
|
minSdkVersion 22
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
@ -24,10 +24,8 @@
|
|||||||
<activity android:name=".MainActivity">
|
<activity android:name=".MainActivity">
|
||||||
<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>
|
BIN
app/src/main/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 28 KiB |
@ -1,51 +1,78 @@
|
|||||||
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.content.Intent
|
import android.content.Intent
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Button
|
import android.util.Log
|
||||||
|
import android.widget.RadioGroup
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private var isYggStarted = false
|
companion object {
|
||||||
|
const val PARAM_PINTENT = "pendingIntent"
|
||||||
|
const val STATUS_START = 1
|
||||||
|
const val STATUS_FINISH = 0
|
||||||
|
const val IPv6: String = "IPv6"
|
||||||
|
private const val TAG="Yggdrasil"
|
||||||
|
private const val VPN_REQUEST_CODE = 0x0F
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
connectRadioGroup.setOnCheckedChangeListener { group, checkedId ->
|
||||||
|
when (checkedId) {
|
||||||
|
R.id.disconnectButton -> stopVpn()
|
||||||
|
R.id.connectButton -> startVpn()
|
||||||
|
else -> { // Note the block
|
||||||
|
|
||||||
val connectButton = findViewById<Button>(R.id.connect_button)
|
}
|
||||||
connectButton.setOnClickListener {
|
|
||||||
if(!isYggStarted) {
|
|
||||||
// Prepare to establish a VPN connection.
|
|
||||||
// This method returns null if the VPN application is already prepared
|
|
||||||
// or if the user has previously consented to the VPN application.
|
|
||||||
// Otherwise, it returns an Intent to a system activity.
|
|
||||||
val vpnIntent = VpnService.prepare(this)
|
|
||||||
if (vpnIntent != null) startActivityForResult(
|
|
||||||
vpnIntent,
|
|
||||||
0x0F
|
|
||||||
)
|
|
||||||
else onActivityResult(0x0F, Activity.RESULT_OK, null)
|
|
||||||
} else {
|
|
||||||
// FIXME fix this shit, this code doesn't stop service for some reasons
|
|
||||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
|
||||||
stopService(intent)
|
|
||||||
connectButton.text = "Connect"
|
|
||||||
isYggStarted = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun stopVpn(){
|
||||||
|
Log.d(TAG,"Stop")
|
||||||
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
|
intent.putExtra("COMMAND", "STOP")
|
||||||
|
startService(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startVpn(){
|
||||||
|
Log.d(TAG,"Start")
|
||||||
|
val intent= VpnService.prepare(this)
|
||||||
|
if (intent!=null){
|
||||||
|
startActivityForResult(intent, VPN_REQUEST_CODE);
|
||||||
|
}else{
|
||||||
|
onActivityResult(VPN_REQUEST_CODE, Activity.RESULT_OK, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 == 0x0F && 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
|
||||||
|
var pi = createPendingResult(TASK_CODE, intent, 0);
|
||||||
|
intent.putExtra("COMMAND", "START")
|
||||||
|
intent.putExtra(PARAM_PINTENT, pi)
|
||||||
startService(intent)
|
startService(intent)
|
||||||
val connectButton = findViewById<Button>(R.id.connect_button)
|
}
|
||||||
connectButton.text = "Disconnect"
|
when (resultCode) {
|
||||||
isYggStarted = true
|
STATUS_START -> print("service started")
|
||||||
|
STATUS_FINISH -> {
|
||||||
|
val result: String = data!!.getStringExtra(IPv6)
|
||||||
|
findViewById<TextView>(R.id.ip).setText(result)
|
||||||
|
}
|
||||||
|
else -> { // Note the block
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package io.github.chronosx88.yggdrasil
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
import android.app.Service
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
|
import android.system.OsConstants
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import dummy.ConduitEndpoint
|
import dummy.ConduitEndpoint
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@ -11,13 +12,9 @@ import kotlinx.coroutines.cancel
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import mobile.Mobile
|
import mobile.Mobile
|
||||||
import mobile.Yggdrasil
|
import mobile.Yggdrasil
|
||||||
import java.io.FileInputStream
|
import java.io.*
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.experimental.or
|
|
||||||
|
|
||||||
|
|
||||||
class YggdrasilTunService : VpnService() {
|
class YggdrasilTunService : VpnService() {
|
||||||
@ -26,7 +23,7 @@ class YggdrasilTunService : VpnService() {
|
|||||||
private val MAX_PACKET_SIZE = Short.MAX_VALUE.toInt()
|
private val MAX_PACKET_SIZE = Short.MAX_VALUE.toInt()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var isRunning: Boolean = false
|
private const val TAG = "Yggdrasil-service"
|
||||||
}
|
}
|
||||||
private var tunInterface: ParcelFileDescriptor? = null
|
private var tunInterface: ParcelFileDescriptor? = null
|
||||||
private lateinit var yggConduitEndpoint: ConduitEndpoint
|
private lateinit var yggConduitEndpoint: ConduitEndpoint
|
||||||
@ -36,17 +33,19 @@ class YggdrasilTunService : VpnService() {
|
|||||||
private lateinit var writeCoroutine: CoroutineContext
|
private lateinit var writeCoroutine: CoroutineContext
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
if (intent?.getStringExtra("COMMAND") == "STOP") {
|
||||||
return Service.START_STICKY
|
stopVpn()
|
||||||
|
}
|
||||||
|
if (intent?.getStringExtra("COMMAND") == "START") {
|
||||||
|
val pi: PendingIntent = intent.getParcelableExtra(MainActivity.PARAM_PINTENT)
|
||||||
|
setupTunInterface(pi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onStartCommand(intent, flags, startId);
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
private fun setupTunInterface(pi: PendingIntent) {
|
||||||
super.onCreate()
|
pi.send(MainActivity.STATUS_START);
|
||||||
isRunning = true
|
|
||||||
setupTunInterface()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupTunInterface() {
|
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
val ygg = Yggdrasil()
|
val ygg = Yggdrasil()
|
||||||
var configJson = Mobile.generateConfigJSON()
|
var configJson = Mobile.generateConfigJSON()
|
||||||
@ -61,19 +60,20 @@ class YggdrasilTunService : VpnService() {
|
|||||||
|
|
||||||
tunInterface = builder
|
tunInterface = builder
|
||||||
.addAddress(address, 7)
|
.addAddress(address, 7)
|
||||||
.addRoute("10.0.0.0", 8)
|
.allowFamily(OsConstants.AF_INET)
|
||||||
.addRoute("172.16.0.0", 12)
|
|
||||||
.addRoute("192.168.0.0", 16)
|
|
||||||
.addRoute("0200::", 7)
|
|
||||||
.setMtu(MAX_PACKET_SIZE)
|
.setMtu(MAX_PACKET_SIZE)
|
||||||
.establish()
|
.establish()
|
||||||
|
|
||||||
tunInputStream = FileInputStream(tunInterface!!.fileDescriptor)
|
tunInputStream = FileInputStream(tunInterface!!.fileDescriptor)
|
||||||
tunOutputStream = FileOutputStream(tunInterface!!.fileDescriptor)
|
tunOutputStream = FileOutputStream(tunInterface!!.fileDescriptor)
|
||||||
readCoroutine = GlobalScope.launch {
|
readCoroutine = GlobalScope.launch {
|
||||||
// FIXME it will throw exception (bad file descriptor) when coroutine will be canceled
|
var buffer = ByteArray(2048)
|
||||||
while (true) {
|
while (true) {
|
||||||
readPacketsFromTun()
|
try{
|
||||||
|
readPacketsFromTun(buffer)
|
||||||
|
} catch (e: IOException){
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeCoroutine = GlobalScope.launch {
|
writeCoroutine = GlobalScope.launch {
|
||||||
@ -81,6 +81,8 @@ class YggdrasilTunService : VpnService() {
|
|||||||
writePacketsToTun()
|
writePacketsToTun()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val intent: Intent = Intent().putExtra(MainActivity.IPv6, address)
|
||||||
|
pi.send(this, MainActivity.STATUS_FINISH, intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fixConfig(config: MutableMap<Any?, Any?>): MutableMap<Any?, Any?> {
|
private fun fixConfig(config: MutableMap<Any?, Any?>): MutableMap<Any?, Any?> {
|
||||||
@ -117,34 +119,20 @@ class YggdrasilTunService : VpnService() {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readPacketsFromTun() {
|
private fun readPacketsFromTun(buffer: ByteArray) {
|
||||||
if(tunInputStream != null) {
|
if(tunInputStream != null) {
|
||||||
var packet: ByteArray = ByteArray(MAX_PACKET_SIZE)
|
|
||||||
// Read the outgoing packet from the input stream.
|
// Read the outgoing packet from the input stream.
|
||||||
var length = tunInputStream!!.read(packet)
|
var length = tunInputStream!!.read(buffer)
|
||||||
|
|
||||||
//System.out.println("packet size:"+packet.size+" "+byteArrayToHex(packet))
|
|
||||||
//System.out.println("buffer size:"+buffer.array().size+" "+byteArrayToHex(buffer.array()))
|
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
// Ignore control messages, which start with zero.
|
var byteBuffer = ByteBuffer.allocate(length);
|
||||||
if (packet.get(0).compareTo(0)!=0) {
|
byteBuffer.put(buffer, 0, length)
|
||||||
var buffer = ByteBuffer.allocate(length);
|
yggConduitEndpoint.send(byteBuffer.array())
|
||||||
buffer.put(packet, 0, length)
|
} else {
|
||||||
buffer.limit(length)
|
Thread.sleep(10)
|
||||||
yggConduitEndpoint.send(buffer.array())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isBufferEmpty(buffer: ByteArray): Boolean {
|
|
||||||
var sum: Byte = 0
|
|
||||||
for (i in buffer) {
|
|
||||||
sum.or(i)
|
|
||||||
}
|
|
||||||
return sum == 0.toByte()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writePacketsToTun() {
|
private fun writePacketsToTun() {
|
||||||
if(tunOutputStream != null) {
|
if(tunOutputStream != null) {
|
||||||
val buffer = yggConduitEndpoint.recv()
|
val buffer = yggConduitEndpoint.recv()
|
||||||
@ -152,12 +140,16 @@ class YggdrasilTunService : VpnService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRevoke() {
|
fun stopVpn() {
|
||||||
super.onRevoke()
|
|
||||||
isRunning = false
|
|
||||||
readCoroutine.cancel()
|
readCoroutine.cancel()
|
||||||
writeCoroutine.cancel()
|
writeCoroutine.cancel()
|
||||||
tunInterface!!.close()
|
tunInterface!!.close()
|
||||||
tunInterface = null
|
tunInterface = null
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
stopSelf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportHeight="108"
|
|
||||||
android:viewportWidth="108">
|
|
||||||
<path
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
|
||||||
android:strokeColor="#00000000"
|
|
||||||
android:strokeWidth="1">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="78.5885"
|
|
||||||
android:endY="90.9159"
|
|
||||||
android:startX="48.7653"
|
|
||||||
android:startY="61.0927"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0"/>
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0"/>
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
|
||||||
android:strokeColor="#00000000"
|
|
||||||
android:strokeWidth="1"/>
|
|
||||||
</vector>
|
|
@ -1,74 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:height="108dp"
|
|
||||||
android:width="108dp"
|
|
||||||
android:viewportHeight="108"
|
|
||||||
android:viewportWidth="108">
|
|
||||||
<path android:fillColor="#008577"
|
|
||||||
android:pathData="M0,0h108v108h-108z"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
</vector>
|
|
8
app/src/main/res/drawable/out_line.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="#80000000" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="@color/green" />
|
||||||
|
</shape>
|
6
app/src/main/res/drawable/toggle_widget_background.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@color/dark_20" android:state_checked="true" />
|
||||||
|
<item android:drawable="@color/dark_10" android:state_pressed="true" />
|
||||||
|
<item android:drawable="@color/dark_5" />
|
||||||
|
</selector>
|
@ -1,18 +1,83 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
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"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity" android:orientation="vertical"
|
android:background="@color/grey"
|
||||||
android:gravity="center">
|
tools:context=".MainActivity">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/connectRadioGroup"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="left">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/ipLabel"
|
||||||
|
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"/>
|
||||||
|
|
||||||
<Button
|
<TextView
|
||||||
android:id="@+id/connect_button"
|
android:id="@+id/ip"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="30dp"
|
||||||
android:text="@string/connect_button"
|
android:background="@color/dark_10"
|
||||||
android:layout_gravity="center"/>
|
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>
|
||||||
|
|
||||||
</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_marginLeft="1dp"
|
||||||
|
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
|
||||||
|
android:id="@+id/connectButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="1dp"
|
||||||
|
android:layout_marginRight="1dp"
|
||||||
|
android:layout_marginBottom="1dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/toggle_widget_background"
|
||||||
|
android:button="@null"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/connect_button"
|
||||||
|
android:textColor="@color/white" />
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@android:color/transparent"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 4.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 6.9 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 12 KiB |
@ -1,6 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="colorPrimary">#008577</color>
|
<color name="colorPrimary">#555555</color>
|
||||||
<color name="colorPrimaryDark">#00574B</color>
|
<color name="colorPrimaryDark">#343334</color>
|
||||||
<color name="colorAccent">#D81B60</color>
|
<color name="colorAccent">#D81B60</color>
|
||||||
|
<color name="white">#ffffff</color>
|
||||||
|
<color name="green">#83cf03</color>
|
||||||
|
<color name="grey">#343334</color>
|
||||||
|
<color name="dark_5">#555555</color>
|
||||||
|
<color name="dark_10">#666666</color>
|
||||||
|
<color name="dark_20">#777777</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<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>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
|
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|