From f164e05e5c7722ab751b8f7951e50759f7543672 Mon Sep 17 00:00:00 2001 From: Revertron <105154+Revertron@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:42:06 +0100 Subject: [PATCH 01/13] Updated graddle and some deps. (#54) * Updated graddle and some deps. * Updated Java version for CI (11 -> 17). --- .github/workflows/android.yml | 4 ++-- app/build.gradle | 5 +++-- app/src/main/AndroidManifest.xml | 3 +-- build.gradle | 2 +- gradle.properties | 6 +++++- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d80c32f..fa17fce 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -34,10 +34,10 @@ jobs: go install golang.org/x/mobile/cmd/gomobile@latest ~/go/bin/gomobile init - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' cache: gradle diff --git a/app/build.gradle b/app/build.gradle index d1b9298..e051329 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ plugins { } android { - compileSdkVersion 33 + compileSdkVersion 34 defaultConfig { applicationId "eu.neilalexander.yggdrasil" @@ -45,12 +45,13 @@ android { kotlinOptions { jvmTarget = '1.8' } + namespace 'eu.neilalexander.yggdrasil' } dependencies { implementation fileTree(include: ['*.aar'], dir: 'libs') implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.core:core-ktx:1.6.0' + implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a6e947e..c91724e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/build.gradle b/build.gradle index 67f7870..f6f831d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.1.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle.properties b/gradle.properties index 2521752..3feb240 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,4 +16,8 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official +#android.enableR8.fullMode=false +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index eb18bbf..7f3c09a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Aug 26 21:38:34 CEST 2023 +#Mon Nov 27 01:27:23 CET 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From d1ebc977fdc415a28187eed8f50cb894116c9fb2 Mon Sep 17 00:00:00 2001 From: Vasyl Gello Date: Wed, 29 Nov 2023 20:49:24 +0000 Subject: [PATCH 02/13] Update core library and tag new version 0.1-016 (#53) * Bump yggdrasil-go submodule to 0.5.4 * Version 0.1-016 --- app/build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/16.txt | 1 + fastlane/metadata/android/ru/changelogs/16.txt | 1 + libs/yggdrasil-go | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/16.txt create mode 100644 fastlane/metadata/android/ru/changelogs/16.txt diff --git a/app/build.gradle b/app/build.gradle index e051329..23c5c5e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "eu.neilalexander.yggdrasil" minSdkVersion 21 targetSdkVersion 33 - versionCode 15 - versionName "0.1-015" + versionCode 16 + versionName "0.1-016" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/fastlane/metadata/android/en-US/changelogs/16.txt b/fastlane/metadata/android/en-US/changelogs/16.txt new file mode 100644 index 0000000..2bf6005 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/16.txt @@ -0,0 +1 @@ +* Updated core Yggdrasil library to 0.5.4 \ No newline at end of file diff --git a/fastlane/metadata/android/ru/changelogs/16.txt b/fastlane/metadata/android/ru/changelogs/16.txt new file mode 100644 index 0000000..66c5dba --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/16.txt @@ -0,0 +1 @@ +* Обновлена библиотека Yggdrasil до 0.5.4 \ No newline at end of file diff --git a/libs/yggdrasil-go b/libs/yggdrasil-go index e5e8c84..6b6cd0b 160000 --- a/libs/yggdrasil-go +++ b/libs/yggdrasil-go @@ -1 +1 @@ -Subproject commit e5e8c84d7c1a030cf0da1076eb6d2e01d2b9c8c3 +Subproject commit 6b6cd0bed553ada887632524994e4de4e839688e From ca45b37baa303c9a5a969e6861f7c4d742d45368 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 31 May 2024 23:32:03 +0100 Subject: [PATCH 03/13] Use peer count to track enabled/connected, remove unused submodule --- .gitmodules | 3 --- .../java/eu/neilalexander/yggdrasil/MainActivity.kt | 10 +++++----- libs/yggdrasil-go | 1 - 3 files changed, 5 insertions(+), 9 deletions(-) delete mode 160000 libs/yggdrasil-go diff --git a/.gitmodules b/.gitmodules index 3340ab4..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "libs/yggdrasil-go"] - path = libs/yggdrasil-go - url = https://github.com/yggdrasil-network/yggdrasil-go diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt index 947dd07..106189c 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt @@ -155,11 +155,11 @@ class MainActivity : AppCompatActivity() { "state" -> { enabledLabel.text = if (intent.getBooleanExtra("started", false)) { var count = 0 - if (intent.hasExtra("tree")) { - val tree = intent.getStringExtra("tree") - if (tree != null && tree != "null") { - val treeState = JSONArray(tree) - count = treeState.length() + if (intent.hasExtra("peers")) { + val peers = intent.getStringExtra("peers") + if (peers != null && peers != "null") { + val peerState = JSONArray(peers) + count = peerState.length() } } if (count == 0) { diff --git a/libs/yggdrasil-go b/libs/yggdrasil-go deleted file mode 160000 index 6b6cd0b..0000000 --- a/libs/yggdrasil-go +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6b6cd0bed553ada887632524994e4de4e839688e From a70563fba22e5e87bd30d58e964ac93b60df16e0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 31 May 2024 23:32:38 +0100 Subject: [PATCH 04/13] Check out Yggdrasil master branch instead of develop --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index fa17fce..a929523 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -23,7 +23,7 @@ jobs: with: repository: yggdrasil-network/yggdrasil-go path: yggdrasil-go - ref: develop + ref: master fetch-depth: 0 - name: Setup Go environment From 6a66960666c718516b89cefffd4d504d3e9ec3f1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 31 May 2024 23:48:30 +0100 Subject: [PATCH 05/13] Version 0.1-017 (Yggdrasil 0.5.6) --- app/build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/17.txt | 1 + fastlane/metadata/android/ru/changelogs/17.txt | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/17.txt create mode 100644 fastlane/metadata/android/ru/changelogs/17.txt diff --git a/app/build.gradle b/app/build.gradle index 23c5c5e..f55d692 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "eu.neilalexander.yggdrasil" minSdkVersion 21 targetSdkVersion 33 - versionCode 16 - versionName "0.1-016" + versionCode 17 + versionName "0.1-017" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/fastlane/metadata/android/en-US/changelogs/17.txt b/fastlane/metadata/android/en-US/changelogs/17.txt new file mode 100644 index 0000000..6e775f8 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/17.txt @@ -0,0 +1 @@ +* Updated core Yggdrasil library to 0.5.6 diff --git a/fastlane/metadata/android/ru/changelogs/17.txt b/fastlane/metadata/android/ru/changelogs/17.txt new file mode 100644 index 0000000..eb0d4a3 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/17.txt @@ -0,0 +1 @@ +* Обновлена библиотека Yggdrasil до 0.5.6 From 060c096cdf065eb3e7b7bf3038923840adeee799 Mon Sep 17 00:00:00 2001 From: Revertron <105154+Revertron@users.noreply.github.com> Date: Fri, 9 Aug 2024 00:24:22 +0200 Subject: [PATCH 06/13] Preparation for release 0.5.7 (#62) * Updated DNS. * Removed tree info from main screen. * Added a link to public peers site. * Updated version and changelogs. --- app/build.gradle | 4 +-- .../eu/neilalexander/yggdrasil/DnsActivity.kt | 9 +++--- .../yggdrasil/GlobalApplication.kt | 26 +++++++++++++++- .../neilalexander/yggdrasil/MainActivity.kt | 3 -- .../yggdrasil/PacketTunnelProvider.kt | 7 ----- .../neilalexander/yggdrasil/PeersActivity.kt | 4 +++ app/src/main/res/layout/activity_main.xml | 30 ++----------------- app/src/main/res/layout/activity_peers.xml | 17 +++++++++++ app/src/main/res/values-ru/strings.xml | 8 ++--- app/src/main/res/values/strings.xml | 6 ++-- .../metadata/android/en-US/changelogs/18.txt | 14 +++++++++ .../metadata/android/ru/changelogs/18.txt | 14 +++++++++ 12 files changed, 90 insertions(+), 52 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/18.txt create mode 100644 fastlane/metadata/android/ru/changelogs/18.txt diff --git a/app/build.gradle b/app/build.gradle index f55d692..20d3136 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "eu.neilalexander.yggdrasil" minSdkVersion 21 targetSdkVersion 33 - versionCode 17 - versionName "0.1-017" + versionCode 18 + versionName "0.1-018" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt index 264a335..f3f9ed8 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt @@ -13,6 +13,7 @@ import androidx.preference.PreferenceManager import com.google.android.material.textfield.TextInputEditText const val KEY_DNS_SERVERS = "dns_servers" +const val KEY_DNS_VERSION = "dns_version" const val KEY_ENABLE_CHROME_FIX = "enable_chrome_fix" class DnsActivity : AppCompatActivity() { @@ -41,10 +42,10 @@ class DnsActivity : AppCompatActivity() { val descriptionRevertron = getString(R.string.dns_server_info_revertron) // Here we can add some other DNS servers in a future defaultDnsServers = hashMapOf( - "302:7991::53" to Pair(getString(R.string.location_amsterdam), descriptionRevertron), - "302:db60::53" to Pair(getString(R.string.location_prague), descriptionRevertron), - "300:6223::53" to Pair(getString(R.string.location_bratislava), descriptionRevertron), - "301:1088::53" to Pair(getString(R.string.location_buffalo), descriptionRevertron), + "308:62:45:62::" to Pair(getString(R.string.location_amsterdam), descriptionRevertron), + "308:84:68:55::" to Pair(getString(R.string.location_frankfurt), descriptionRevertron), + "308:25:40:bd::" to Pair(getString(R.string.location_bratislava), descriptionRevertron), + "308:c8:48:45::" to Pair(getString(R.string.location_buffalo), descriptionRevertron), ) serversTableLayout = findViewById(R.id.configuredDnsTableLayout) diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt b/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt index 85f9a09..49a544b 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt @@ -8,6 +8,7 @@ import android.os.Build import android.service.quicksettings.TileService import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat +import androidx.preference.PreferenceManager const val PREF_KEY_ENABLED = "enabled" const val MAIN_CHANNEL_ID = "Yggdrasil Service" @@ -15,7 +16,7 @@ const val MAIN_CHANNEL_ID = "Yggdrasil Service" class GlobalApplication: Application(), YggStateReceiver.StateReceiver { private lateinit var config: ConfigurationProxy private var currentState: State = State.Disabled - var updaterConnections: Int = 0 + private var updaterConnections: Int = 0 override fun onCreate() { super.onCreate() @@ -24,6 +25,7 @@ class GlobalApplication: Application(), YggStateReceiver.StateReceiver { callback.register() val receiver = YggStateReceiver(this) receiver.register(this) + migrateDnsServers(this) } fun subscribe() { @@ -64,6 +66,28 @@ class GlobalApplication: Application(), YggStateReceiver.StateReceiver { } } +fun migrateDnsServers(context: Context) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + if (preferences.getInt(KEY_DNS_VERSION, 0) >= 1) { + return + } + val serverString = preferences.getString(KEY_DNS_SERVERS, "") + if (serverString!!.isNotEmpty()) { + // Replacing old Revertron's servers by new ones + val newServers = serverString + .replace("300:6223::53", "308:25:40:bd::") + .replace("302:7991::53", "308:62:45:62::") + .replace("302:db60::53", "308:84:68:55::") + .replace("301:1088::53", "308:c8:48:45::") + val editor = preferences.edit() + editor.putInt(KEY_DNS_VERSION, 1) + if (newServers != serverString) { + editor.putString(KEY_DNS_SERVERS, newServers) + } + editor.apply() + } +} + fun createServiceNotification(context: Context, state: State): Notification { createNotificationChannels(context) diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt index 106189c..d1cd613 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt @@ -24,7 +24,6 @@ class MainActivity : AppCompatActivity() { private lateinit var enabledLabel: TextView private lateinit var ipAddressLabel: TextView private lateinit var subnetLabel: TextView - private lateinit var treeLengthLabel: TextView private lateinit var peersLabel: TextView private lateinit var peersRow: LinearLayoutCompat private lateinit var dnsLabel: TextView @@ -53,7 +52,6 @@ class MainActivity : AppCompatActivity() { enabledLabel = findViewById(R.id.yggdrasilStatusLabel) ipAddressLabel = findViewById(R.id.ipAddressValue) subnetLabel = findViewById(R.id.subnetValue) - treeLengthLabel = findViewById(R.id.treeLengthValue) peersLabel = findViewById(R.id.peersValue) peersRow = findViewById(R.id.peersTableRow) dnsLabel = findViewById(R.id.dnsValue) @@ -175,7 +173,6 @@ class MainActivity : AppCompatActivity() { } ipAddressLabel.text = intent.getStringExtra("ip") ?: "N/A" subnetLabel.text = intent.getStringExtra("subnet") ?: "N/A" - treeLengthLabel.text = intent.getStringExtra("coords") ?: "0" if (intent.hasExtra("peers")) { val peerState = JSONArray(intent.getStringExtra("peers") ?: "[]") peersLabel.text = when (val count = peerState.length()) { diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt b/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt index 7259e80..c5519f6 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt @@ -236,11 +236,6 @@ open class PacketTunnelProvider: VpnService() { var lastStateUpdate = System.currentTimeMillis() updates@ while (started.get()) { val treeJSON = yggdrasil.treeJSON - var treeLength = 0 - if (treeJSON != null && treeJSON != "null") { - val treeState = JSONArray(treeJSON) - treeLength = treeState.length() - } if ((application as GlobalApplication).needUiUpdates()) { val intent = Intent(STATE_INTENT) intent.putExtra("type", "state") @@ -248,9 +243,7 @@ open class PacketTunnelProvider: VpnService() { intent.putExtra("ip", yggdrasil.addressString) intent.putExtra("subnet", yggdrasil.subnetString) intent.putExtra("pubkey", yggdrasil.publicKeyString) - intent.putExtra("coords", "$treeLength") intent.putExtra("peers", yggdrasil.peersJSON) - intent.putExtra("tree", treeJSON) LocalBroadcastManager.getInstance(this).sendBroadcast(intent) } val curTime = System.currentTimeMillis() diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt index 8e67280..030ffa1 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt @@ -7,6 +7,7 @@ import android.content.Intent import android.content.IntentFilter import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.text.method.LinkMovementMethod import android.util.Log import android.view.ContextThemeWrapper import android.view.KeyEvent @@ -47,6 +48,9 @@ class PeersActivity : AppCompatActivity() { configuredTableLayout = findViewById(R.id.configuredPeersTableLayout) configuredTableLabel = findViewById(R.id.configuredPeersLabel) + val discoveryLink = findViewById(R.id.peers_discovery_link) + discoveryLink.movementMethod = LinkMovementMethod.getInstance() + multicastListenSwitch = findViewById(R.id.enableMulticastListen) multicastListenSwitch.setOnCheckedChangeListener { button, _ -> config.multicastListen = button.isChecked diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a974a99..9261130 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -116,7 +116,7 @@ android:layout_height="32px" /> @@ -203,32 +203,6 @@ - - - - - - - + + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 35be1ee..ba34ea4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -42,11 +42,10 @@ Сброс Состояние Включить Yggdrasil - Статистика + Адрес и сеть Н/Д Адрес Подсеть - Высота дерева Конфигурация Пиры Серверы DNS @@ -58,6 +57,7 @@ Находимый через multicast Искать пиров через multicast Yggdrasil будет пытаться подключаться к этим пирам автоматически. Если вы добавите несколько пиров, ваше устройство может быть использовано для переноса данных между другими узлами сети. Чтобы этого избежать настройте только один пир. + Вы можете найти публичные пиры по этой ссылке. Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB. У них должен быть одинаковый пароль. Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства. Пароль Об узле @@ -73,9 +73,9 @@ Включено (Нет подключения) Подключено Амстердам, Нидерланды - Прага, Чехия + Франкфурт, Германия Братислава, Словакия - Баффало, США + Буффало, США Сервис VPN Главный канал нотификаций сервиса Нажмите здесь чтобы включить Yggdrasil. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7bf2dac..f886f02 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,11 +42,10 @@ Reset Status Enable Yggdrasil - Statistics + Network info N/A IP Subnet - Tree length Configuration Peers DNS servers @@ -58,6 +57,7 @@ Discoverable over multicast Search for multicast peers Yggdrasil will automatically attempt to connect to configured peers when started. If you configure more than one peer, your device may carry traffic on behalf of other network nodes. Avoid this by configuring only a single peer. + You can find public peers by opening this link. Multicast peers will be discovered on the same Wi-Fi network or via USB. They must have the same password. Data charges may apply when using mobile data. You can prevent data usage in the device settings. Password Node Info @@ -73,7 +73,7 @@ Enabled (No connectivity) Connected Amsterdam, NL - Prague, CZ + Frankfurt, DE Bratislava, SK Buffalo, US VPN Service diff --git a/fastlane/metadata/android/en-US/changelogs/18.txt b/fastlane/metadata/android/en-US/changelogs/18.txt new file mode 100644 index 0000000..7c13f7b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/18.txt @@ -0,0 +1,14 @@ +Updated core library to 0.5.7, presenting these changes: + +Added +WebSocket support for peerings, by using the new ws:// scheme in Listen and Peers +Additionally, the wss:// scheme can be used to connect to a WebSocket peer behind a HTTPS reverse proxy + +Changed +On Linux, the TUN adapter now uses vectorised reads/writes where possible, which should reduce the amount of CPU time spent on syscalls and potentially improve throughput +Link error handling has been improved and various link error messages have been rewritten to be clearer +Upgrade dependencies + +Fixed +Multiple multicast connections to the same remote machine should now work correctly +You may get two connections in some cases, one inbound and one outbound, this is known and will not cause problems diff --git a/fastlane/metadata/android/ru/changelogs/18.txt b/fastlane/metadata/android/ru/changelogs/18.txt new file mode 100644 index 0000000..fe1c795 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/18.txt @@ -0,0 +1,14 @@ +Обновлена основная библиотека до версии 0.5.7, в которой представлены следующие изменения: + +Добавлено +Поддержка WebSocket для пиринга с использованием новой схемы ws:// в Listen и Peers +Кроме того, схему wss:// можно использовать для подключения к узлам WebSocket за обратным прокси-сервером HTTPS вроде Nginx + +Изменено +В Linux адаптер TUN теперь использует векторизованную чтение/запись, где это возможно, что должно сократить количество времени ЦП, затрачиваемого на системные вызовы, и потенциально повысить пропускную способность +Улучшена обработка ошибок соединения, а различные сообщения об ошибках соединения были переписаны для большей ясности +Обновление зависимостей + +Исправлено +Несколько мультикаст подключений к одной и той же удаленной машине теперь должны работать правильно +В некоторых случаях вы можете получить два подключения, одно входящее и одно исходящее, это известное поведение и не вызовет проблем \ No newline at end of file From 34756b21938704d6bbd9ddcc2c01b295ad163112 Mon Sep 17 00:00:00 2001 From: Revertron <105154+Revertron@users.noreply.github.com> Date: Mon, 19 Aug 2024 00:07:45 +0200 Subject: [PATCH 07/13] Returned the yggdrasil-go submodule. (#63) * Returned the yggdrasil-go submodule. --- .gitmodules | 3 +++ libs/yggdrasil-go | 1 + 2 files changed, 4 insertions(+) create mode 160000 libs/yggdrasil-go diff --git a/.gitmodules b/.gitmodules index e69de29..3340ab4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libs/yggdrasil-go"] + path = libs/yggdrasil-go + url = https://github.com/yggdrasil-network/yggdrasil-go diff --git a/libs/yggdrasil-go b/libs/yggdrasil-go new file mode 160000 index 0000000..947b6ad --- /dev/null +++ b/libs/yggdrasil-go @@ -0,0 +1 @@ +Subproject commit 947b6ad7aa93eb2174bf16ddac844c7afaf2677d From cdc12a8e7ebeb188703de3999cd571f2b458e3e9 Mon Sep 17 00:00:00 2001 From: Revertron <105154+Revertron@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:10:01 +0200 Subject: [PATCH 08/13] Prepare for release 0.5.9 (#68) Updated the yggdrasil-go submodule. Fixed #64, updated some deps. --- app/build.gradle | 18 +++++++++--------- app/src/main/AndroidManifest.xml | 2 ++ .../eu/neilalexander/yggdrasil/MainActivity.kt | 4 ++++ .../yggdrasil/PacketTunnelProvider.kt | 6 +++++- build.gradle | 2 +- libs/yggdrasil-go | 2 +- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 20d3136..7bbf932 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,9 +9,9 @@ android { defaultConfig { applicationId "eu.neilalexander.yggdrasil" minSdkVersion 21 - targetSdkVersion 33 - versionCode 18 - versionName "0.1-018" + targetSdkVersion 34 + versionCode 19 + versionName "0.1-019" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -51,12 +51,12 @@ android { dependencies { implementation fileTree(include: ['*.aar'], dir: 'libs') implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.preference:preference-ktx:1.2.1' - testImplementation 'junit:junit:4.+' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c91724e..ebf9dd1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt index d1cd613..bc5f716 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt @@ -68,6 +68,7 @@ class MainActivity : AppCompatActivity() { startVpnActivity.launch(vpnIntent) } else { start() + enabledSwitch.isEnabled = false } } false -> { @@ -181,6 +182,9 @@ class MainActivity : AppCompatActivity() { else -> getString(R.string.main_many_peers, count) } } + if (!enabledSwitch.isEnabled) { + enabledSwitch.isEnabled = true + } } } } diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt b/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt index c5519f6..aa84803 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt @@ -232,7 +232,11 @@ open class PacketTunnelProvider: VpnService() { } private fun updater() { - Thread.sleep(500) + try { + Thread.sleep(500) + } catch (_: InterruptedException) { + return + } var lastStateUpdate = System.currentTimeMillis() updates@ while (started.get()) { val treeJSON = yggdrasil.treeJSON diff --git a/build.gradle b/build.gradle index f6f831d..2f8ae41 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.9.10' + ext.kotlin_version = '1.9.20' repositories { google() mavenCentral() diff --git a/libs/yggdrasil-go b/libs/yggdrasil-go index 947b6ad..0b9c8bd 160000 --- a/libs/yggdrasil-go +++ b/libs/yggdrasil-go @@ -1 +1 @@ -Subproject commit 947b6ad7aa93eb2174bf16ddac844c7afaf2677d +Subproject commit 0b9c8bd020f971847604e7e1f0e0cfc66e65da49 From e211111d60dcd833a1158f70fc2a19b0578dd956 Mon Sep 17 00:00:00 2001 From: Revertron <105154+Revertron@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:11:31 +0200 Subject: [PATCH 09/13] Updated release-notes for F-Droid. (#69) --- .../metadata/android/en-US/changelogs/19.txt | 23 +++++++++++++++++++ .../metadata/android/ru/changelogs/19.txt | 23 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/19.txt create mode 100644 fastlane/metadata/android/ru/changelogs/19.txt diff --git a/fastlane/metadata/android/en-US/changelogs/19.txt b/fastlane/metadata/android/en-US/changelogs/19.txt new file mode 100644 index 0000000..da2178d --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/19.txt @@ -0,0 +1,23 @@ +Updated core library to 0.5.9, presenting these changes: + +Changed +The routing algorithm has been updated with RTT-aware link costing, which should prefer lower latency links over higher latency links where possible +The calculated cost is an average of the link RTT, but newly established links are costed higher to begin with, such that unstable peerings can be avoided +Link costs are only used where multiple next-hops are available and will be ignored if there is only one loop-free path to the destination +This is protocol-compatible with existing v0.5.x nodes but will have the best results when peering with nodes that are also running the latest version +The getPeers endpoint will now report the calculated link cost for each given peer +Upgrade dependencies + +Fixed +Multicast discovery should now work again when building Yggdrasil as an Android framework +Multicast discovery will now correctly ignore interfaces that are not marked as running +Ephemeral links, such as those added by multicast, will no longer try to reconnect in a fast loop, fixing a high CPU issue +The TUN interface will no longer stop working when hitting a segment read error from vectorised reads +The AllowedPublicKeys option will once again no longer apply to multicast peerings, as was originally intended +A potential panic when shutting down peering links has been fixed +A redundant system call for setting MTU on OpenBSD has been removed + +Fixes in Android app +Fixed occasional crash on start/stop +Updated some dependencies +Updated Android API to 34 \ No newline at end of file diff --git a/fastlane/metadata/android/ru/changelogs/19.txt b/fastlane/metadata/android/ru/changelogs/19.txt new file mode 100644 index 0000000..4bbe5b8 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/19.txt @@ -0,0 +1,23 @@ +Обновлена основная библиотека до версии 0.5.9, в которой представлены следующие изменения: + +Изменено +Алгоритм маршрутизации был обновлен с учетом стоимости соединения с RTT, что должно отдавать предпочтения соединениям с меньшей задержкой соединениям с большей задержкой, когда это возможно +Расчетная стоимость представляет собой среднее значение RTT соединения, но новые соединения изначально оцениваются выше, так что можно избежать проблем с нестабильными узлами +Стоимость соединения используется только при наличии нескольких следующих переходов и будет игнорироваться, если есть только один путь без петель к месту назначения +Эта версия совместима с существующими узлами v0.5.x, но будет иметь наилучшие результаты при пиринге с узлами, которые также работают под управлением последней версии +Команда getPeers теперь будет сообщать рассчитанную стоимость соединения для каждого заданного пира +Обновлены зависимости + +Исправлено +Обнаружение локальных пиров теперь должно снова работать при сборке Yggdrasil как фреймворка Android +Обнаружение локальных пиров теперь будет правильно игнорировать интерфейсы, которые не помечены как работающие +Эфемерные соединения, такие как добавленные мультикастом, больше не будут пытаться быстро переподключаться в цикле, устранена проблема высокой загрузки ЦП +Интерфейс TUN больше не будет прекращать работу при срабатывании ошибки чтения пакета с помощью векторизованного чтения +Опция AllowedPublicKeys снова больше не будет применяться к локальным пирам, как изначально предполагалось +Потенциальный краш при отключении пиринговых соединений был исправлен +Избыточный системный вызов для установки MTU в OpenBSD был удален + +Исправления в приложении для Android +Исправлен случайный сбой при запуске/остановке +Обновлены некоторые зависимости +Обновлен Android API до 34 \ No newline at end of file From 94db1facd7d901103e8f56a968176a17eff59c0e Mon Sep 17 00:00:00 2001 From: Poussinou Date: Mon, 25 Nov 2024 13:26:15 -0500 Subject: [PATCH 10/13] Update readme.md (#41) Signed-off-by: Poussinou Co-authored-by: Revertron <105154+Revertron@users.noreply.github.com> --- readme.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 69fe52e..98e1bc2 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,23 @@ -## build instructions +Yggdrasil Android +----------------- + +Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6 network. It is lightweight, self-arranging, supported on multiple platforms and allows pretty much any IPv6-capable application to communicate securely with other Yggdrasil nodes. Yggdrasil does not require you to have IPv6 Internet connectivity - it also works over IPv4. + +This app allows you to connect to Yggdrasil Network and use any service located in this network. It works as VPN service, but all your usual traffic will go trough your provider, not through Yggdrasil Network. + +Also, it is not a goal of the Yggdrasil project to provide anonymity. Direct peers over the Internet will be able to see your IP address and may be able to use this information to determine your location or identity. Multicast-discovered peerings on the same network will typically expose your device MAC address. Other nodes on the network may be able to discern some information about which nodes you are peered with. + +All traffic sent across the Yggdrasil network is encrypted end-to-end. Assuming that our crypto is solid, it cannot be decrypted or read by any intermediate nodes, and can only be decrypted by the recipient for which it was intended. However, please note that Yggdrasil has not been officially externally audited. + +## Download + +[Get it on F-Droid](https://f-droid.org/packages/eu.neilalexander.yggdrasil/) + +Or get the APK from the [Releases Section](https://github.com/yggdrasil-network/yggdrasil-android/releases/latest). + +## Build Instructions * install gomobile From 055a74ea6904b4945484e7abf7e74f5cf41bc63a Mon Sep 17 00:00:00 2001 From: Revertron <105154+Revertron@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:13:56 +0100 Subject: [PATCH 11/13] UI improvements (#74) * Fixed connected peers status in PeersActivity. * Fixed display of new generated public key in SettingsActivity. * Made more reliable display of connectivity state on main screen. * Added a note about not configured peers. Changed all dialog buttons to greenish color. * Click on a version row now opens URL of the project on GitHub. * Changed the wording of no peer notification. --- .../yggdrasil/ConfigurationProxy.kt | 2 +- .../eu/neilalexander/yggdrasil/DnsActivity.kt | 6 +- .../yggdrasil/GlobalApplication.kt | 1 + .../neilalexander/yggdrasil/MainActivity.kt | 65 ++++++++++++++++--- .../neilalexander/yggdrasil/PeersActivity.kt | 35 +++++++--- .../yggdrasil/SettingsActivity.kt | 14 ++-- app/src/main/res/layout/activity_main.xml | 4 +- app/src/main/res/values-ru/strings.xml | 3 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/themes.xml | 4 ++ 11 files changed, 109 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/ConfigurationProxy.kt b/app/src/main/java/eu/neilalexander/yggdrasil/ConfigurationProxy.kt index 0047679..2eca90a 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/ConfigurationProxy.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/ConfigurationProxy.kt @@ -55,7 +55,7 @@ object ConfigurationProxy { json.put("IfMTU", 65535) if (json.getJSONArray("MulticastInterfaces").get(0) is String) { - var ar = JSONArray() + val ar = JSONArray() ar.put(0, JSONObject(""" { "Regex": ".*", diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt index f3f9ed8..bd50644 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/DnsActivity.kt @@ -57,7 +57,7 @@ class DnsActivity : AppCompatActivity() { addServerButton.setOnClickListener { val view = inflater.inflate(R.layout.dialog_add_dns_server, null) val input = view.findViewById(R.id.addDnsInput) - val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) + val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs)) builder.setTitle(getString(R.string.dns_add_server_dialog_title)) builder.setView(view) builder.setPositiveButton(getString(R.string.add)) { _, _ -> @@ -128,7 +128,7 @@ class DnsActivity : AppCompatActivity() { view.findViewById(R.id.deletePeerButton).tag = i view.findViewById(R.id.deletePeerButton).setOnClickListener { button -> - val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) + val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs)) builder.setTitle(getString(R.string.dns_remove_title, server)) builder.setPositiveButton(getString(R.string.remove)) { dialog, _ -> servers.removeAt(button.tag as Int) @@ -176,7 +176,7 @@ class DnsActivity : AppCompatActivity() { } } view.setOnLongClickListener { - val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) + val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs)) builder.setTitle(getString(R.string.dns_server_info_dialog_title)) builder.setMessage("${infoPair.first}\n\n${infoPair.second}") builder.setPositiveButton(getString(R.string.ok)) { dialog, _ -> diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt b/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt index 49a544b..0431d12 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt @@ -11,6 +11,7 @@ import androidx.core.app.NotificationCompat import androidx.preference.PreferenceManager const val PREF_KEY_ENABLED = "enabled" +const val PREF_KEY_PEERS_NOTE = "peers_note" const val MAIN_CHANNEL_ID = "Yggdrasil Service" class GlobalApplication: Application(), YggStateReceiver.StateReceiver { diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt index bc5f716..befdbe3 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt @@ -1,10 +1,13 @@ package eu.neilalexander.yggdrasil import android.app.Activity +import android.app.AlertDialog import android.content.* import android.graphics.Color +import android.net.Uri import android.net.VpnService import android.os.Bundle +import android.view.ContextThemeWrapper import android.widget.Switch import android.widget.TextView import android.widget.Toast @@ -18,6 +21,7 @@ import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT import mobile.Mobile import org.json.JSONArray +const val APP_WEB_URL = "https://github.com/yggdrasil-network/yggdrasil-android" class MainActivity : AppCompatActivity() { private lateinit var enabledSwitch: Switch @@ -29,6 +33,7 @@ class MainActivity : AppCompatActivity() { private lateinit var dnsLabel: TextView private lateinit var dnsRow: LinearLayoutCompat private lateinit var settingsRow: LinearLayoutCompat + private lateinit var versionRow: LinearLayoutCompat private fun start() { val intent = Intent(this, PacketTunnelProvider::class.java) @@ -57,6 +62,7 @@ class MainActivity : AppCompatActivity() { dnsLabel = findViewById(R.id.dnsValue) dnsRow = findViewById(R.id.dnsTableRow) settingsRow = findViewById(R.id.settingsTableRow) + versionRow = findViewById(R.id.versionTableRow) enabledLabel.setTextColor(Color.GRAY) @@ -104,6 +110,11 @@ class MainActivity : AppCompatActivity() { startActivity(intent) } + versionRow.isClickable = true + versionRow.setOnClickListener { + openUrlInBrowser(APP_WEB_URL) + } + ipAddressLabel.setOnLongClickListener { val clipboard: ClipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("ip", ipAddressLabel.text) @@ -152,15 +163,16 @@ class MainActivity : AppCompatActivity() { override fun onReceive(context: Context?, intent: Intent) { when (intent.getStringExtra("type")) { "state" -> { - enabledLabel.text = if (intent.getBooleanExtra("started", false)) { - var count = 0 - if (intent.hasExtra("peers")) { - val peers = intent.getStringExtra("peers") - if (peers != null && peers != "null") { - val peerState = JSONArray(peers) - count = peerState.length() - } + val peerState = JSONArray(intent.getStringExtra("peers") ?: "[]") + var count = 0 + for (i in 0.. getString(R.string.main_no_peers) 1 -> getString(R.string.main_one_peer) else -> getString(R.string.main_many_peers, count) @@ -189,4 +200,38 @@ class MainActivity : AppCompatActivity() { } } } + + private fun showPeersNoteIfNeeded(peerCount: Int) { + if (peerCount > 0) return + val preferences = PreferenceManager.getDefaultSharedPreferences(this@MainActivity.baseContext) + if (!preferences.getBoolean(PREF_KEY_PEERS_NOTE, false)) { + this@MainActivity.runOnUiThread { + val builder: AlertDialog.Builder = + AlertDialog.Builder(ContextThemeWrapper(this@MainActivity, R.style.YggdrasilDialogs)) + builder.setTitle(getString(R.string.main_add_some_peers_title)) + builder.setMessage(getString(R.string.main_add_some_peers_message)) + builder.setPositiveButton(getString(R.string.ok)) { dialog, _ -> + dialog.dismiss() + } + builder.show() + } + // Mark this note as shown + preferences.edit().apply { + putBoolean(PREF_KEY_PEERS_NOTE, true) + commit() + } + } + } + + fun openUrlInBrowser(url: String) { + val intent = Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(url) + } + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + // Handle the exception if no browser is found + Toast.makeText(this, getText(R.string.no_browser_found_toast), Toast.LENGTH_SHORT).show() + } + } } diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt index 030ffa1..55e2817 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt @@ -5,7 +5,6 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.text.method.LinkMovementMethod import android.util.Log @@ -13,13 +12,20 @@ import android.view.ContextThemeWrapper import android.view.KeyEvent import android.view.LayoutInflater import android.view.View -import android.widget.* +import android.widget.EditText +import android.widget.ImageButton +import android.widget.Switch +import android.widget.TableLayout +import android.widget.TableRow +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity import androidx.core.widget.doOnTextChanged import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.google.android.material.textfield.TextInputEditText import org.json.JSONArray import org.json.JSONObject + class PeersActivity : AppCompatActivity() { private lateinit var config: ConfigurationProxy private lateinit var inflater: LayoutInflater @@ -99,7 +105,7 @@ class PeersActivity : AppCompatActivity() { addPeerButton.setOnClickListener { val view = inflater.inflate(R.layout.dialog_addpeer, null) val input = view.findViewById(R.id.addPeerInput) - val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) + val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs)) builder.setTitle(getString(R.string.peers_add_peer)) builder.setView(view) builder.setPositiveButton(getString(R.string.peers_add)) { dialog, _ -> @@ -153,7 +159,7 @@ class PeersActivity : AppCompatActivity() { view.findViewById(R.id.deletePeerButton).tag = i view.findViewById(R.id.deletePeerButton).setOnClickListener { button -> - val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) + val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs)) builder.setTitle(getString(R.string.peers_remove_title, peer)) builder.setPositiveButton(getString(R.string.peers_remove)) { dialog, _ -> config.updateJSON { json -> @@ -180,16 +186,25 @@ class PeersActivity : AppCompatActivity() { connectedTableLabel.text = getString(R.string.peers_no_connected_title) } else -> { - connectedTableLayout.visibility = View.VISIBLE - connectedTableLabel.text = getString(R.string.peers_connected_title) - + var connected = false connectedTableLayout.removeAllViewsInLayout() for (peer in peers) { val view = inflater.inflate(R.layout.peers_connected, null) val ip = peer.getString("IP") - view.findViewById(R.id.addressLabel).text = ip - view.findViewById(R.id.detailsLabel).text = peer.getString("URI") - connectedTableLayout.addView(view) + // Only connected peers have IPs + if (ip.isNotEmpty()) { + view.findViewById(R.id.addressLabel).text = ip + view.findViewById(R.id.detailsLabel).text = peer.getString("URI") + connectedTableLayout.addView(view) + connected = true + } + } + if (connected) { + connectedTableLayout.visibility = View.VISIBLE + connectedTableLabel.text = getString(R.string.peers_connected_title) + } else { + connectedTableLayout.visibility = View.GONE + connectedTableLabel.text = getString(R.string.peers_no_connected_title) } } } diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/SettingsActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/SettingsActivity.kt index 5831776..8e2e3d2 100644 --- a/app/src/main/java/eu/neilalexander/yggdrasil/SettingsActivity.kt +++ b/app/src/main/java/eu/neilalexander/yggdrasil/SettingsActivity.kt @@ -27,6 +27,7 @@ class SettingsActivity : AppCompatActivity() { private lateinit var deviceNameEntry: EditText private lateinit var publicKeyLabel: TextView private lateinit var resetConfigurationRow: LinearLayoutCompat + private var publicKeyReset = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -69,7 +70,7 @@ class SettingsActivity : AppCompatActivity() { resetConfigurationRow.setOnClickListener { val view = inflater.inflate(R.layout.dialog_resetconfig, null) - val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) + val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs)) builder.setTitle(getString(R.string.settings_warning_title)) builder.setView(view) builder.setPositiveButton(getString(R.string.settings_reset)) { dialog, _ -> @@ -85,12 +86,13 @@ class SettingsActivity : AppCompatActivity() { findViewById(R.id.resetKeysRow).setOnClickListener { config.resetKeys() + publicKeyReset = true updateView() } findViewById(R.id.setKeysRow).setOnClickListener { val view = inflater.inflate(R.layout.dialog_set_keys, null) - val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) + val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs)) val privateKey = view.findViewById(R.id.private_key) builder.setTitle(getString(R.string.set_keys)) builder.setView(view) @@ -125,7 +127,11 @@ class SettingsActivity : AppCompatActivity() { deviceNameEntry.setText("", TextView.BufferType.EDITABLE) } - publicKeyLabel.text = json.optString("PublicKey") + var key = json.optString("PrivateKey") + if (key.isNotEmpty()) { + key = key.substring(key.length / 2) + } + publicKeyLabel.text = key } override fun onResume() { @@ -145,7 +151,7 @@ class SettingsActivity : AppCompatActivity() { // To be able to get public key from running Yggdrasil we use this receiver, as we don't have this field in config private val receiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent) { - if (intent.hasExtra("pubkey")) { + if (intent.hasExtra("pubkey") && !publicKeyReset) { val tree = intent.getStringExtra("pubkey") if (tree != null && tree != "null") { publicKeyLabel.text = intent.getStringExtra("pubkey") diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9261130..e85a344 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -351,7 +351,9 @@ - + Нет пиров 1 пир %d пира/пиров + Внимание + Не настроено ни одного пира. Если не будет обнаруживаемых пиров в этой сети, то вам надо будет добавить пир вручную, чтобы подключение к Yggdrasil работало как положено. Добавить пира в конфиг Добавить Убрать %s? @@ -83,4 +85,5 @@ Приватный ключ: Установить свой ключ Сохранить + Не найден браузер для открытия ссылки! \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 4f74376..2c8e64a 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,6 +5,7 @@ #FF3700B3 #FF03DAC5 #FF018786 + #5FBF9F #FF000000 #FFFFFFFF #F2F1F5 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f886f02..61c8633 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,8 @@ No peers 1 peer %d peers + Note + No peers are configured. If there are no multicast peers nearby, you will need to manually configure peers in order for Yggdrasil to connect and work properly. Add Configured Peer Add Remove %s? @@ -83,4 +85,5 @@ Private key: Set your own key Save + No browser found to open the URL! \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index ece6c2c..e48c592 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -18,4 +18,8 @@ @color/white @color/black + + \ No newline at end of file From 6ddc878fde5b315d78415637bdf11cc5132b0c1e Mon Sep 17 00:00:00 2001 From: Revertron <105154+Revertron@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:56:34 +0100 Subject: [PATCH 12/13] Prepared the 0.5.12 release. (#77) * Prepared the 0.5.12 release. --- app/build.gradle | 4 ++-- .../metadata/android/en-US/changelogs/20.txt | 19 +++++++++++++++++++ .../metadata/android/ru/changelogs/20.txt | 19 +++++++++++++++++++ libs/yggdrasil-go | 2 +- 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/20.txt create mode 100644 fastlane/metadata/android/ru/changelogs/20.txt diff --git a/app/build.gradle b/app/build.gradle index 7bbf932..1a38c8d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "eu.neilalexander.yggdrasil" minSdkVersion 21 targetSdkVersion 34 - versionCode 19 - versionName "0.1-019" + versionCode 20 + versionName "0.1-020" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/fastlane/metadata/android/en-US/changelogs/20.txt b/fastlane/metadata/android/en-US/changelogs/20.txt new file mode 100644 index 0000000..8641cf5 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/20.txt @@ -0,0 +1,19 @@ +Updated core library to 0.5.12, presenting these changes: + +Fixed +A timing regression which causes a higher level of idle protocol traffic on each peering has been fixed + +Fixes in Android app +Some UI fixes and improvements +Updated some dependencies + +Updates from previous versions: + +Changed +The parent selection algorithm now only chooses a new parent if there is a larger cost benefit to doing so, which should help to stabilise the tree +The bloom filters are now repropagated periodically, to avoid nodes getting stuck with bad state + +Fixed +A memory leak caused by missed cleanup of the peer response map has been fixed +Other bug fixes with bloom filter propagation for off-tree filters and zero vs one bits +TLS-based peering connections now support TLS 1.2 again diff --git a/fastlane/metadata/android/ru/changelogs/20.txt b/fastlane/metadata/android/ru/changelogs/20.txt new file mode 100644 index 0000000..ef12299 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/20.txt @@ -0,0 +1,19 @@ +Обновлена основная библиотека до версии 0.5.12, в которой представлены следующие изменения: + +Исправлено +Исправлена регрессия синхронизации, которая приводит к более высокому уровню служебного трафика в простое + +Исправления в приложении Android +Исправления и улучшения пользовательского интерфейса +Обновлены некоторые зависимости + +Обновления с предыдущих версий: + +Изменено +Алгоритм выбора родителя теперь выбирает нового родителя только в том случае, если это дает большую экономическую выгоду, что должно помочь стабилизировать дерево +Фильтры Блума теперь периодически распространяются повторно, чтобы избежать застревания узлов в плохом состоянии + +Исправлено +Утечка памяти, вызванная пропущенной очисткой карты ответов пиров +Другие исправления ошибок с распространением фильтра Блума для фильтров вне дерева +Пиринг с использованием TLS теперь снова поддерживают TLS 1.2 \ No newline at end of file diff --git a/libs/yggdrasil-go b/libs/yggdrasil-go index 0b9c8bd..213f72b 160000 --- a/libs/yggdrasil-go +++ b/libs/yggdrasil-go @@ -1 +1 @@ -Subproject commit 0b9c8bd020f971847604e7e1f0e0cfc66e65da49 +Subproject commit 213f72b8403ff55a5e38a0fa7d1cd0a093ac4666 From 222d9d90bc6bd1c10dacf2657b74b4db6a2d6c70 Mon Sep 17 00:00:00 2001 From: Leo Heitmann Ruiz Date: Tue, 11 Feb 2025 00:19:27 +0100 Subject: [PATCH 13/13] Add icon for F-Droid (#79) --- fastlane/metadata/android/en-US/images/icon.png | Bin 0 -> 8021 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 fastlane/metadata/android/en-US/images/icon.png diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..124727e92ed7d7c14ab2e605fdac935321c7386d GIT binary patch literal 8021 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

l&tQ5V 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 literal 0 HcmV?d00001