Compare commits

..

139 Commits

Author SHA1 Message Date
Zane Schepke 1952ff1b02 Merge remote-tracking branch 'weblate/main' into localize-sync
# Conflicts:
#	app/src/main/res/values-cs/strings.xml
#	app/src/main/res/values-fr/strings.xml
#	app/src/main/res/values-hu/strings.xml
#	app/src/main/res/values-ru/strings.xml
#	app/src/main/res/values-ur/strings.xml
#	app/src/main/res/values-zh-rTW/strings.xml
#	fastlane/metadata/android/zh-TW/short_description.txt
2025-07-11 12:57:56 -04:00
Andras 99f6c0bda5 Translated using Weblate (Hungarian)
Currently translated at 5.3% (14 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/hu/
2025-07-11 18:48:04 +02:00
Zane Schepke 8b828cca55 fix: nightly installer permission bug 2025-07-06 04:13:59 -04:00
Priit Jõerüüt 28b77d33ca Translated using Weblate (Estonian)
Currently translated at 90.1% (237 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-07-06 06:05:19 +02:00
Priit Jõerüüt b0b070f6aa Translated using Weblate (Estonian)
Currently translated at 90.1% (237 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-07-06 06:05:19 +02:00
Priit Jõerüüt 89b1f314b1 Translated using Weblate (Estonian)
Currently translated at 90.1% (237 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-07-06 06:05:19 +02:00
Zane Schepke a223289949 feat: add shizuku support (#852) 2025-07-05 20:49:02 -04:00
EESF-2 020263eba6 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 88.2% (232 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hant/
2025-07-01 22:02:05 +02:00
EESF-2 d359215b20 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 25.0% (3 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/zh_Hant/
2025-07-01 22:02:03 +02:00
Priit Jõerüüt a6a0bba569 Translated using Weblate (Estonian)
Currently translated at 81.7% (215 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-06-29 23:04:36 +02:00
Priit Jõerüüt f729e924cd Translated using Weblate (Estonian)
Currently translated at 81.7% (215 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-06-29 23:04:35 +02:00
teemue 3c1d0f893a Translated using Weblate (Finnish)
Currently translated at 52.8% (139 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/fi/
2025-06-29 23:04:34 +02:00
teemue b184331258 Translated using Weblate (Finnish)
Currently translated at 52.8% (139 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/fi/
2025-06-29 23:04:33 +02:00
Priit Jõerüüt 36603ab542 Translated using Weblate (Estonian)
Currently translated at 76.8% (202 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-06-28 13:04:37 +00:00
Priit Jõerüüt d0e21247b5 Translated using Weblate (Estonian)
Currently translated at 76.8% (202 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-06-28 13:04:35 +00:00
Priit Jõerüüt 786a8b54ce Translated using Weblate (Estonian)
Currently translated at 76.8% (202 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-06-28 13:04:34 +00:00
Priit Jõerüüt d762fe7283 Translated using Weblate (Estonian)
Currently translated at 76.8% (202 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-06-28 13:04:33 +00:00
Jan-Erik Moen ed9cab3251 Translated using Weblate (Norwegian Bokmål)
Currently translated at 5.3% (14 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/nb_NO/
2025-06-28 13:04:32 +00:00
Priit Jõerüüt 8e1e754a87 Translated using Weblate (Estonian)
Currently translated at 6.0% (16 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/et/
2025-06-27 07:01:52 +02:00
Priit Jõerüüt c6eaaa1f37 Translated using Weblate (Estonian)
Currently translated at 33.3% (4 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/et/
2025-06-27 07:01:50 +02:00
Languages add-on 5077ec9a46 Added translation using Weblate (Estonian) 2025-06-26 06:22:22 +02:00
catelixor e4a808c6ce Translated using Weblate (Czech)
Currently translated at 100.0% (263 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/cs/
2025-06-26 00:04:44 +00:00
Zane Schepke c8b65fb7fa ci: fix token 2025-06-19 00:58:34 -04:00
EESF-2 52efdfa288 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 86.3% (227 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hant/
2025-06-19 01:04:27 +02:00
Zane Schepke feec7f0ffc chore: bump version 2025-06-17 15:43:46 -04:00
Zane Schepke b63c6a9b73 fix: simplify update check dialog ui 2025-06-17 15:34:24 -04:00
Zane Schepke 46975607c4 fix: version check name change 2025-06-17 14:04:00 -04:00
Zane Schepke 0c7bcb5453 fix: nightly version check 2025-06-14 16:59:40 -04:00
Zane Schepke 599bf9c9e0 fix: wifi name surrounding quotes, prevent multiple auto-tunnel jobs
#768
#797
2025-06-14 15:39:22 -04:00
Zane Schepke 03345bdf86 fix!: deprecated wifi info api, add detection option selection
This will break for users who have selected get tunnel name via root shell. They will need to select the new option on update. Also, this will cause more location pings. Users who want less location pings will have to enable the legacy api option manually.

#768
#797
2025-06-12 21:49:48 -04:00
Zane Schepke b07e604003 chore: bump deps 2025-06-11 07:16:24 -04:00
igor a4f413e79e Translated using Weblate (French)
Currently translated at 76.4% (201 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/fr/
2025-06-10 17:01:52 +02:00
igor 71ae3864fc Translated using Weblate (French)
Currently translated at 58.3% (7 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/fr/
2025-06-10 17:01:50 +02:00
Hamed Ap 008bc476e0 Translated using Weblate (Persian)
Currently translated at 11.0% (29 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/fa/
2025-06-09 10:02:02 +02:00
Hamed Ap 3947b04e21 Translated using Weblate (Persian)
Currently translated at 100.0% (12 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/fa/
2025-06-09 10:01:59 +02:00
Languages add-on acef2637c8 Added translation using Weblate (Persian) 2025-06-08 07:10:21 +00:00
Noureddine ba8b09cdc9 Translated using Weblate (Arabic)
Currently translated at 8.3% (1 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/ar/
2025-06-07 07:01:47 +02:00
Languages add-on 79e5ba0cb0 Added translation using Weblate (Arabic) 2025-06-06 04:41:28 +00:00
Zane Schepke c8b3af4857 refactor: format 2025-05-28 04:07:50 -04:00
Zane Schepke 0a3447c63d fix: improve auto tunnel reliability with delayed check
refactor: add more auto tunnel logging

#797
2025-05-28 04:06:26 -04:00
Zane Schepke 7f3297db79 fix: rapid toggling crash bug, typo 2025-05-28 02:25:25 -04:00
catelixor 165fda0352 Translated using Weblate (Czech)
Currently translated at 98.8% (260 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/cs/
2025-05-24 09:54:40 +02:00
Faisal Gull c4dfb4d591 Translated using Weblate (Urdu)
Currently translated at 100.0% (12 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/ur/
2025-05-19 05:03:13 +02:00
Faisal Gull e80e0b7f94 Translated using Weblate (Urdu)
Currently translated at 100.0% (263 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ur/
2025-05-19 05:03:10 +02:00
Zane Schepke aa33aebd2f chore: add full description fastlane 2025-05-16 05:51:13 -04:00
Zane Schepke 53b1d03ca8 ci: permission fix 2025-05-16 05:28:39 -04:00
Zane Schepke 53f72850e2 refactor: remove redundant pt 2025-05-16 05:10:33 -04:00
Zane Schepke b8deb7b644 chore: bump version
ci: add tag deploy
2025-05-16 01:38:01 -04:00
Zane Schepke d5a3090782 chore(deps): bump ksp, kotlin, agp 2025-05-16 00:23:00 -04:00
Zane Schepke 063cbf3ea6 fix: active network tracking bug
#768
closes #789
2025-05-16 00:07:54 -04:00
François-Xavier Choinière b856fc2230 Translated using Weblate (French)
Currently translated at 75.6% (199 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/fr/
2025-05-16 04:02:07 +02:00
Deleted User 202ac52e25 Translated using Weblate (Spanish)
Currently translated at 100.0% (263 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/es/
2025-05-13 19:02:26 +02:00
Saratoga79 2b1a8af998 Translated using Weblate (Spanish)
Currently translated at 100.0% (263 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/es/
2025-05-13 19:02:25 +02:00
Zane Schepke 4a45387efd fix: fdroid reproducibility baseline profile bug
closes #780
2025-05-12 02:30:10 -04:00
Zane Schepke fa064ef3a6 fix: qr scanner bug, disable beep
closes #776
2025-05-12 02:16:49 -04:00
Zane Schepke 3f8894a566 chore(deps): bump deps 2025-05-12 02:03:37 -04:00
solokot b924845835 Translated using Weblate (Russian)
Currently translated at 99.6% (262 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2025-05-11 08:01:43 +02:00
catelixor 0f8d7fed97 Translated using Weblate (Czech)
Currently translated at 97.7% (257 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/cs/
2025-05-09 23:01:51 +00:00
angrybb 20ff172055 Translated using Weblate (Serbian)
Currently translated at 16.6% (2 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/sr/
2025-05-07 23:01:49 +02:00
Faisal Gull 42671f616f Translated using Weblate (Urdu)
Currently translated at 100.0% (12 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/ur/
2025-05-07 23:01:48 +02:00
Faisal Gull ecca99828f Translated using Weblate (Urdu)
Currently translated at 100.0% (263 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ur/
2025-05-07 23:01:46 +02:00
Weblate (bot) 6d77ef878d Translations update from Hosted Weblate (#702)
Co-authored-by: Matthaiks <kitynska@gmail.com>
Co-authored-by: kometchtech <kometch@gmail.com>
Co-authored-by: 翻譯得真好下次別翻了 <x86_64-pc-linux-gnu@proton.me>
Co-authored-by: solokot <solokot@gmail.com>
Co-authored-by: Kachelkaiser <kachelkaiser@htpst.de>
Co-authored-by: catelixor <catelixor+weblate@proton.me>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Faisal Gull <mail.faisalrehman.345@gmail.com>
Co-authored-by: vm <varga.m007@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: sgauthiertremblay <info@sgauthiertremblay.dev>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: Valentin <velentin.s@yandex.ru>
Co-authored-by: adkostatt <adkostatt@gmail.com>
Co-authored-by: VertekPlus <vertekplus@users.noreply.hosted.weblate.org>
Co-authored-by: Jasper <jasper@ennik.com>
Co-authored-by: Tommaso <mrduckhunt@users.noreply.hosted.weblate.org>
Co-authored-by: dct <dct@trnh.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
2025-05-06 20:58:29 -04:00
Languages add-on 7f40df9d36 Added translation using Weblate (Serbian) 2025-05-06 22:16:27 +02:00
dct 9a497f7892 Translated using Weblate (Vietnamese)
Currently translated at 5.7% (15 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/vi/
2025-05-06 22:16:26 +02:00
Tommaso dc5a10ebc9 Translated using Weblate (Italian)
Currently translated at 100.0% (12 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/it/
2025-05-06 14:06:03 +00:00
catelixor d1a4c7f133 Translated using Weblate (Czech)
Currently translated at 92.7% (244 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/cs/
2025-05-06 14:06:01 +00:00
catelixor 93ebf299e6 Translated using Weblate (Czech)
Currently translated at 100.0% (12 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/cs/
2025-05-06 14:06:00 +00:00
大王叫我来巡山 1d83da9d44 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (263 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hans/
2025-05-05 10:01:55 +02:00
Matthaiks d2abaad5b4 Translated using Weblate (Polish)
Currently translated at 100.0% (263 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/pl/
2025-05-05 10:01:54 +02:00
Matthaiks 8bf88a89bd Translated using Weblate (Polish)
Currently translated at 100.0% (12 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/pl/
2025-05-05 10:01:52 +02:00
Kachelkaiser 96cfd04450 Translated using Weblate (German)
Currently translated at 100.0% (263 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/de/
2025-05-05 10:01:51 +02:00
Kachelkaiser c5f39ec906 Translated using Weblate (German)
Currently translated at 100.0% (12 of 12 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/de/
2025-05-05 10:01:49 +02:00
solokot 5cd8f9c2f2 Translated using Weblate (Russian)
Currently translated at 99.6% (262 of 263 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2025-05-05 10:01:47 +02:00
Tommaso 2d11712fdc Translated using Weblate (Italian)
Currently translated at 96.5% (250 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/it/
2025-05-04 09:03:30 +02:00
Tommaso 9528850873 Translated using Weblate (Italian)
Currently translated at 100.0% (11 of 11 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/it/
2025-05-04 09:03:30 +02:00
Faisal Gull a0d5647f23 Translated using Weblate (Urdu)
Currently translated at 100.0% (259 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ur/
2025-05-04 09:03:30 +02:00
Faisal Gull 5ff810a6ef Translated using Weblate (Urdu)
Currently translated at 100.0% (11 of 11 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/ur/
2025-05-04 09:03:30 +02:00
Faisal Gull 3dc314d79a Translated using Weblate (Urdu)
Currently translated at 100.0% (259 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ur/
2025-05-04 09:03:30 +02:00
Jasper 466a5cecbe Translated using Weblate (Dutch)
Currently translated at 70.6% (183 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/nl/
2025-05-04 09:03:30 +02:00
VertekPlus a13d70227f Translated using Weblate (Russian)
Currently translated at 99.6% (258 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2025-05-04 09:03:30 +02:00
adkostatt 519981d681 Translated using Weblate (Russian)
Currently translated at 99.6% (258 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2025-05-04 09:03:30 +02:00
Valentin 0d17bf28e0 Translated using Weblate (Russian)
Currently translated at 99.6% (258 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2025-05-04 09:03:30 +02:00
ssantos fc1d4b22a6 Translated using Weblate (Portuguese)
Currently translated at 18.1% (2 of 11 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/pt/
2025-05-04 09:03:30 +02:00
翻譯得真好下次別翻了 d2f5be6e19 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 72.5% (188 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hant/
2025-05-04 09:03:30 +02:00
Kachelkaiser 27a5b6b9f2 Translated using Weblate (German)
Currently translated at 100.0% (11 of 11 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/de/
2025-05-04 09:03:30 +02:00
大王叫我来巡山 2ae048b1de Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (259 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hans/
2025-05-04 09:03:30 +02:00
Matthaiks a890c83088 Translated using Weblate (Polish)
Currently translated at 100.0% (259 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/pl/
2025-05-04 09:03:30 +02:00
Kachelkaiser 3eedbdccba Translated using Weblate (German)
Currently translated at 100.0% (259 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/de/
2025-05-04 09:03:30 +02:00
solokot 73b3d03a25 Translated using Weblate (Russian)
Currently translated at 100.0% (259 of 259 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2025-05-04 09:03:30 +02:00
catelixor b74f84abc2 Translated using Weblate (Czech)
Currently translated at 89.9% (232 of 258 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/cs/
2025-05-04 09:03:30 +02:00
Matthaiks bbb056e9d1 Translated using Weblate (Polish)
Currently translated at 100.0% (258 of 258 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/pl/
2025-05-04 09:03:30 +02:00
தமிழ்நேரம் 0d953a32be Translated using Weblate (Tamil)
Currently translated at 100.0% (256 of 256 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ta/
2025-05-04 09:03:30 +02:00
sgauthiertremblay 3383bb6b45 Translated using Weblate (French)
Currently translated at 77.3% (198 of 256 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/fr/
2025-05-04 09:03:30 +02:00
தமிழ்நேரம் afcb801806 Translated using Weblate (Tamil)
Currently translated at 100.0% (11 of 11 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/ta/
2025-05-04 09:03:30 +02:00
大王叫我来巡山 494835a5a4 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (256 of 256 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hans/
2025-05-04 09:03:30 +02:00
catelixor 2f784f563f Translated using Weblate (Czech)
Currently translated at 89.8% (230 of 256 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/cs/
2025-05-04 09:03:30 +02:00
catelixor 11d6fd2ed9 Translated using Weblate (Czech)
Currently translated at 63.6% (7 of 11 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/cs/
2025-05-04 09:03:30 +02:00
Matthaiks 0e8138ba6b Translated using Weblate (Polish)
Currently translated at 100.0% (256 of 256 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/pl/
2025-05-04 09:03:30 +02:00
Matthaiks 0eb3719972 Translated using Weblate (Polish)
Currently translated at 100.0% (11 of 11 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/pl/
2025-05-04 09:03:30 +02:00
solokot 196cd34ef7 Translated using Weblate (Russian)
Currently translated at 100.0% (256 of 256 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2025-05-04 09:03:30 +02:00
Matthaiks ed42a2f7df Translated using Weblate (Polish)
Currently translated at 99.2% (254 of 256 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/pl/
2025-05-04 09:03:30 +02:00
vm 9b944bf51e Translated using Weblate (Hungarian)
Currently translated at 20.0% (2 of 10 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/hu/
2025-05-04 09:03:30 +02:00
Faisal Gull b8c792418d Translated using Weblate (Urdu)
Currently translated at 100.0% (254 of 254 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ur/
2025-05-04 09:03:30 +02:00
vm 6d8a38c65b Translated using Weblate (Hungarian)
Currently translated at 5.1% (13 of 254 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/hu/
2025-05-04 09:03:30 +02:00
翻譯得真好下次別翻了 09c1c68aad Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 72.4% (184 of 254 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hant/
2025-05-04 09:03:30 +02:00
大王叫我来巡山 36b8353b07 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (254 of 254 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hans/
2025-05-04 09:03:30 +02:00
catelixor f10ed1269e Translated using Weblate (Czech)
Currently translated at 87.7% (223 of 254 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/cs/
2025-05-04 09:03:30 +02:00
Matthaiks e6e35f01d7 Translated using Weblate (Polish)
Currently translated at 100.0% (254 of 254 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/pl/
2025-05-04 09:03:30 +02:00
Kachelkaiser 35737bdd84 Translated using Weblate (German)
Currently translated at 100.0% (254 of 254 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/de/
2025-05-04 09:03:30 +02:00
solokot 5a6960bffd Translated using Weblate (Russian)
Currently translated at 100.0% (254 of 254 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2025-05-04 09:03:30 +02:00
翻譯得真好下次別翻了 10cefe2ab0 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 70.8% (170 of 240 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hant/
2025-05-04 09:03:30 +02:00
翻譯得真好下次別翻了 add53834e7 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 30.0% (3 of 10 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/zh_Hant/
2025-05-04 09:03:30 +02:00
catelixor f740ec57ea Translated using Weblate (Czech)
Currently translated at 80.0% (192 of 240 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/cs/
2025-05-04 09:03:30 +02:00
catelixor 7eadfe2af2 Translated using Weblate (Czech)
Currently translated at 50.0% (5 of 10 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/cs/
2025-05-04 09:03:30 +02:00
Faisal Gull 001a4358e1 Translated using Weblate (Urdu)
Currently translated at 100.0% (10 of 10 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/ur/
2025-05-04 09:03:30 +02:00
Faisal Gull 31abe64513 Translated using Weblate (Urdu)
Currently translated at 100.0% (240 of 240 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ur/
2025-05-04 09:03:30 +02:00
翻譯得真好下次別翻了 900aa86c29 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 68.7% (165 of 240 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hant/
2025-05-04 09:03:29 +02:00
大王叫我来巡山 264e17f30b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (240 of 240 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hans/
2025-05-04 09:03:29 +02:00
catelixor fbc3ff3276 Translated using Weblate (Czech)
Currently translated at 73.7% (177 of 240 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/cs/
2025-05-04 09:03:29 +02:00
Kachelkaiser 151cdd4e03 Translated using Weblate (German)
Currently translated at 100.0% (240 of 240 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/de/
2025-05-04 09:03:29 +02:00
Kachelkaiser f0deb1294a Translated using Weblate (German)
Currently translated at 100.0% (10 of 10 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/de/
2025-05-04 09:03:29 +02:00
solokot 19efb08cb9 Translated using Weblate (Russian)
Currently translated at 100.0% (240 of 240 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2025-05-04 09:03:29 +02:00
Matthaiks 9f4b9ca771 Translated using Weblate (Polish)
Currently translated at 100.0% (240 of 240 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/pl/
2025-05-04 09:03:29 +02:00
翻譯得真好下次別翻了 a60e480be0 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 63.8% (152 of 238 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hant/
2025-05-04 09:03:29 +02:00
kometchtech f28e21ef79 Translated using Weblate (Japanese)
Currently translated at 41.1% (98 of 238 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ja/
2025-05-04 09:03:29 +02:00
Matthaiks b4ca1e63c4 Translated using Weblate (Polish)
Currently translated at 100.0% (10 of 10 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/pl/
2025-05-04 09:03:29 +02:00
翻譯得真好下次別翻了 e35f4bbb52 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 63.4% (151 of 238 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/zh_Hant/
2025-05-04 09:03:29 +02:00
kometchtech f0e575da69 Translated using Weblate (Japanese)
Currently translated at 39.0% (93 of 238 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ja/
2025-05-04 09:03:29 +02:00
kometchtech a6b7d2091a Translated using Weblate (Japanese)
Currently translated at 100.0% (9 of 9 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/ja/
2025-05-04 09:03:29 +02:00
Matthaiks 324a1a66c5 Translated using Weblate (Polish)
Currently translated at 100.0% (9 of 9 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/pl/
2025-05-04 09:03:29 +02:00
Zane Schepke a05f11739d chore: bump version with notes 2025-05-01 15:25:09 -04:00
Zane Schepke fe519be5cc refactor: remove use cache setting 2025-05-01 15:16:16 -04:00
dependabot[bot] ab0f67c897 chore(deps): bump com.zaneschepke:wireguard-android from 1.2.16 to 1.3.0 (#772)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-01 14:56:25 -04:00
dependabot[bot] a5639cd129 chore(deps): bump com.google.devtools.ksp from 2.1.20-2.0.0 to 2.1.20-2.0.1 (#774)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-01 14:56:08 -04:00
dependabot[bot] babbab051f chore(deps): bump org.semver4j:semver4j from 5.6.0 to 5.7.0 (#773)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-01 14:55:54 -04:00
dependabot[bot] 2e984e8b6f chore(deps): bump accompanist from 0.37.2 to 0.37.3 (#762)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-01 14:55:42 -04:00
Zane Schepke 044e6da7f5 fix: tunnel connectivity bug (#769) 2025-05-01 14:55:15 -04:00
Zane Schepke 77aa2c30d7 feat: display qr for individual tunnels 2025-04-30 06:23:23 -04:00
Zane Schepke e773238e6b ci: refactor and fix bugs (#767) 2025-04-29 07:31:18 -04:00
204 changed files with 3303 additions and 803 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Check for new commits - name: Check for new commits
id: check id: check
env: env:
GITHUB_TOKEN: ${{ secrets.PAT }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
NEW_COMMITS=$(git rev-list --count --after="$(date -Iseconds -d '23 hours ago')" ${{ github.sha }}) NEW_COMMITS=$(git rev-list --count --after="$(date -Iseconds -d '23 hours ago')" ${{ github.sha }})
echo "new_commits=$NEW_COMMITS" >> $GITHUB_OUTPUT echo "new_commits=$NEW_COMMITS" >> $GITHUB_OUTPUT
+19 -14
View File
@@ -4,6 +4,9 @@ permissions:
packages: write packages: write
on: on:
push:
tags:
- '[0-9]*.[0-9]*.[0-9]*'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
track: track:
@@ -49,19 +52,19 @@ on:
jobs: jobs:
build-fdroid: build-fdroid:
if: ${{ inputs.release_type == 'release' || inputs.flavor == 'fdroid' }} if: ${{ github.event_name == 'push' || inputs.release_type == 'release' || inputs.flavor == 'fdroid' }}
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
secrets: inherit secrets: inherit
with: with:
build_type: ${{ inputs.release_type }} build_type: ${{ github.event_name == 'push' && 'release' || inputs.release_type }}
flavor: fdroid flavor: fdroid
build-standalone: build-standalone:
if: ${{ inputs.release_type == 'release' || inputs.release_type == 'prerelease' || inputs.flavor == 'standalone' }} if: ${{ github.event_name == 'push' || inputs.release_type == 'release' || inputs.release_type == 'prerelease' || inputs.flavor == 'standalone' }}
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
secrets: inherit secrets: inherit
with: with:
build_type: ${{ inputs.release_type }} build_type: ${{ github.event_name == 'push' && 'release' || inputs.release_type }}
flavor: standalone flavor: standalone
publish: publish:
@@ -72,7 +75,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
ref: main ref: ${{ github.event_name == 'push' && github.ref || 'main' }}
- name: Install system dependencies - name: Install system dependencies
run: | run: |
sudo apt update && sudo apt install -y gh apksigner sudo apt update && sudo apt install -y gh apksigner
@@ -113,7 +116,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Set version release notes - name: Set version release notes
if: ${{ inputs.release_type == 'release' }} if: ${{ github.event_name == 'push' || inputs.release_type == 'release' }}
run: | run: |
VERSION_NAME=$(grep "const val VERSION_NAME" buildSrc/src/main/kotlin/Constants.kt | awk -F'"' '{print $2}') VERSION_NAME=$(grep "const val VERSION_NAME" buildSrc/src/main/kotlin/Constants.kt | awk -F'"' '{print $2}')
RELEASE_NOTES="$(cat ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/${VERSION_NAME}.txt || echo "No changelog found for ${VERSION_NAME}")" RELEASE_NOTES="$(cat ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/${VERSION_NAME}.txt || echo "No changelog found for ${VERSION_NAME}")"
@@ -122,7 +125,7 @@ jobs:
echo "EOF" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV
- name: On prerelease release notes - name: On prerelease release notes
if: ${{ inputs.release_type == 'prerelease' }} if: ${{ github.event_name != 'push' && inputs.release_type == 'prerelease' }}
run: | run: |
echo "RELEASE_NOTES=Testing version of app for specific feature." >> $GITHUB_ENV echo "RELEASE_NOTES=Testing version of app for specific feature." >> $GITHUB_ENV
@@ -156,11 +159,11 @@ jobs:
### Changelog ### Changelog
${{ steps.changelog.outputs.changes }} ${{ steps.changelog.outputs.changes }}
tag_name: ${{ github.event.inputs.tag_name }} tag_name: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag_name }}
name: ${{ github.event.inputs.tag_name }} name: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag_name }}
draft: false draft: false
prerelease: ${{ inputs.release_type == 'prerelease' }} prerelease: ${{ github.event_name != 'push' && inputs.release_type == 'prerelease' }}
make_latest: ${{ inputs.release_type == 'release' }} make_latest: ${{ github.event_name == 'push' || inputs.release_type == 'release' }}
files: | files: |
${{ github.workspace }}/temp/**/*.apk ${{ github.workspace }}/temp/**/*.apk
env: env:
@@ -170,7 +173,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- build-fdroid - build-fdroid
if: inputs.release_type == 'release' if: ${{ github.event_name == 'push' || inputs.release_type == 'release' }}
steps: steps:
- name: Dispatch update for fdroid repo - name: Dispatch update for fdroid repo
uses: peter-evans/repository-dispatch@v3 uses: peter-evans/repository-dispatch@v3
@@ -180,7 +183,7 @@ jobs:
event-type: fdroid-update event-type: fdroid-update
publish-play: publish-play:
if: ${{ inputs.track != 'none' }} if: ${{ github.event_name == 'push' || inputs.track != 'none' }}
name: Publish to Google Play name: Publish to Google Play
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -230,4 +233,6 @@ jobs:
bundler-cache: true bundler-cache: true
- name: Distribute app to Prod track 🚀 - name: Distribute app to Prod track 🚀
run: (cd ${{ github.workspace }} && bundle install && bundle exec fastlane ${{ inputs.track }}) run: |
track=${{ github.event_name == 'push' && 'production' || inputs.track }}
(cd ${{ github.workspace }} && bundle install && bundle exec fastlane $track)
+15 -4
View File
@@ -110,9 +110,9 @@ android {
} }
compileOptions { compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
} }
kotlinOptions { jvmTarget = Constants.JVM_TARGET } kotlinOptions { jvmTarget = Constants.JVM_TARGET }
buildFeatures { buildFeatures {
@@ -123,8 +123,7 @@ android {
licensee { licensee {
Constants.allowedLicenses.forEach { allow(it) } Constants.allowedLicenses.forEach { allow(it) }
allowUrl(Constants.XZING_LICENSE_URL) Constants.allowedLicenseUrls.forEach { allowUrl(it) }
allowUrl("https://rafaellins.mit-license.org/2021/")
} }
applicationVariants.all { applicationVariants.all {
@@ -198,6 +197,7 @@ dependencies {
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.zxing.android.embedded) implementation(libs.zxing.android.embedded)
implementation(libs.material.icons.extended) implementation(libs.material.icons.extended)
implementation(libs.androidx.biometric.ktx) implementation(libs.androidx.biometric.ktx)
@@ -210,7 +210,7 @@ dependencies {
implementation(libs.androidx.work.runtime) implementation(libs.androidx.work.runtime)
implementation(libs.androidx.hilt.work) implementation(libs.androidx.hilt.work)
implementation(libs.qrcode.kotlin) implementation(libs.qrose)
implementation(libs.semver4j) implementation(libs.semver4j)
implementation(libs.ktor.client.core) implementation(libs.ktor.client.core)
@@ -219,6 +219,10 @@ dependencies {
implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.slf4j.android) implementation(libs.slf4j.android)
// shizuku
implementation(libs.shizuku.api)
implementation(libs.shizuku.provider)
} }
tasks.register<Copy>("copyLicenseeJsonToAssets") { tasks.register<Copy>("copyLicenseeJsonToAssets") {
@@ -231,3 +235,10 @@ tasks.register<Copy>("copyLicenseeJsonToAssets") {
} }
tasks.named("preBuild") { dependsOn("copyLicenseeJsonToAssets") } tasks.named("preBuild") { dependsOn("copyLicenseeJsonToAssets") }
// https://gist.github.com/obfusk/61046e09cee352ae6dd109911534b12e#fix-proposed-by-linsui-disable-baseline-profiles
tasks.whenTaskAdded {
if (name.contains("ArtProfile")) {
enabled = false
}
}
@@ -155,9 +155,7 @@
"columnNames": [ "columnNames": [
"id" "id"
] ]
}, }
"indices": [],
"foreignKeys": []
}, },
{ {
"tableName": "TunnelConfig", "tableName": "TunnelConfig",
@@ -227,21 +225,18 @@
"fieldPath": "pingInterval", "fieldPath": "pingInterval",
"columnName": "ping_interval", "columnName": "ping_interval",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": false,
"defaultValue": "null" "defaultValue": "null"
}, },
{ {
"fieldPath": "pingCooldown", "fieldPath": "pingCooldown",
"columnName": "ping_cooldown", "columnName": "ping_cooldown",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": false,
"defaultValue": "null" "defaultValue": "null"
}, },
{ {
"fieldPath": "pingIp", "fieldPath": "pingIp",
"columnName": "ping_ip", "columnName": "ping_ip",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false,
"defaultValue": "null" "defaultValue": "null"
}, },
{ {
@@ -275,11 +270,9 @@
"orders": [], "orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)" "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
} }
], ]
"foreignKeys": []
} }
], ],
"views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ae51793c4d09ea3194ecd26f0606f35c')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ae51793c4d09ea3194ecd26f0606f35c')"
@@ -0,0 +1,295 @@
{
"formatVersion": 1,
"database": {
"version": 17,
"identityHash": "380d82359c99933cc9ce783347c4ec31",
"entities": [
{
"tableName": "Settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT false, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT false, `is_vpn_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_lan_on_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `debounce_delay_seconds` INTEGER NOT NULL DEFAULT 3, `is_disable_kill_switch_on_trusted_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT false, `split_tunnel_apps` TEXT NOT NULL DEFAULT '', `wifi_detection_method` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isAutoTunnelEnabled",
"columnName": "is_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isTunnelOnMobileDataEnabled",
"columnName": "is_tunnel_on_mobile_data_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "trustedNetworkSSIDs",
"columnName": "trusted_network_ssids",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAlwaysOnVpnEnabled",
"columnName": "is_always_on_vpn_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isTunnelOnEthernetEnabled",
"columnName": "is_tunnel_on_ethernet_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isShortcutsEnabled",
"columnName": "is_shortcuts_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isTunnelOnWifiEnabled",
"columnName": "is_tunnel_on_wifi_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isKernelEnabled",
"columnName": "is_kernel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isRestoreOnBootEnabled",
"columnName": "is_restore_on_boot_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isMultiTunnelEnabled",
"columnName": "is_multi_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isPingEnabled",
"columnName": "is_ping_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isAmneziaEnabled",
"columnName": "is_amnezia_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isWildcardsEnabled",
"columnName": "is_wildcards_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isStopOnNoInternetEnabled",
"columnName": "is_stop_on_no_internet_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isVpnKillSwitchEnabled",
"columnName": "is_vpn_kill_switch_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isKernelKillSwitchEnabled",
"columnName": "is_kernel_kill_switch_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isLanOnKillSwitchEnabled",
"columnName": "is_lan_on_kill_switch_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "debounceDelaySeconds",
"columnName": "debounce_delay_seconds",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "3"
},
{
"fieldPath": "isDisableKillSwitchOnTrustedEnabled",
"columnName": "is_disable_kill_switch_on_trusted_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isTunnelOnUnsecureEnabled",
"columnName": "is_tunnel_on_unsecure_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "splitTunnelApps",
"columnName": "split_tunnel_apps",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "wifiDetectionMethod",
"columnName": "wifi_detection_method",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "TunnelConfig",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '', `is_Active` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `ping_interval` INTEGER DEFAULT null, `ping_cooldown` INTEGER DEFAULT null, `ping_ip` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `is_ipv4_preferred` INTEGER NOT NULL DEFAULT true)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "wgQuick",
"columnName": "wg_quick",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tunnelNetworks",
"columnName": "tunnel_networks",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "isMobileDataTunnel",
"columnName": "is_mobile_data_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isPrimaryTunnel",
"columnName": "is_primary_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "amQuick",
"columnName": "am_quick",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "isActive",
"columnName": "is_Active",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isPingEnabled",
"columnName": "is_ping_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "pingInterval",
"columnName": "ping_interval",
"affinity": "INTEGER",
"defaultValue": "null"
},
{
"fieldPath": "pingCooldown",
"columnName": "ping_cooldown",
"affinity": "INTEGER",
"defaultValue": "null"
},
{
"fieldPath": "pingIp",
"columnName": "ping_ip",
"affinity": "TEXT",
"defaultValue": "null"
},
{
"fieldPath": "isEthernetTunnel",
"columnName": "is_ethernet_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isIpv4Preferred",
"columnName": "is_ipv4_preferred",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "true"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_TunnelConfig_name",
"unique": true,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '380d82359c99933cc9ce783347c4ec31')"
]
}
}
+4 -4
View File
@@ -62,6 +62,10 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.App.Start" android:theme="@style/Theme.App.Start"
tools:targetApi="tiramisu"> tools:targetApi="tiramisu">
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="portrait"
tools:replace="screenOrientation" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@@ -78,10 +82,6 @@
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" /> <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="portrait"
tools:replace="screenOrientation" />
<activity <activity
android:name=".core.shortcut.ShortcutsActivity" android:name=".core.shortcut.ShortcutsActivity"
@@ -22,7 +22,6 @@ import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -50,11 +49,11 @@ import com.zaneschepke.wireguardautotunnel.ui.navigation.components.DynamicTopAp
import com.zaneschepke.wireguardautotunnel.ui.navigation.components.currentNavBackStackEntryAsNavBarState import com.zaneschepke.wireguardautotunnel.ui.navigation.components.currentNavBackStackEntryAsNavBarState
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.AutoTunnelScreen import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.AutoTunnelScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced.AutoTunnelAdvancedScreen import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.advanced.AutoTunnelAdvancedScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.detection.WifiDetectionMethodScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.LocationDisclosureScreen import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.disclosure.LocationDisclosureScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.TunnelAutoTunnelScreen import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.TunnelAutoTunnelScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.ConfigScreen import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.ConfigScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.main.scanner.ScannerScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.SplitTunnelScreen import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.SplitTunnelScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.TunnelOptionsScreen import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.TunnelOptionsScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.pin.PinLockScreen import com.zaneschepke.wireguardautotunnel.ui.screens.pin.PinLockScreen
@@ -88,6 +87,8 @@ class MainActivity : AppCompatActivity() {
private var lastLocationPermissionState: Boolean? = null private var lastLocationPermissionState: Boolean? = null
val REQUEST_CODE = 123
@SuppressLint("BatteryLife") @SuppressLint("BatteryLife")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge( enableEdgeToEdge(
@@ -286,6 +287,9 @@ class MainActivity : AppCompatActivity() {
composable<Route.AutoTunnelAdvanced> { composable<Route.AutoTunnelAdvanced> {
AutoTunnelAdvancedScreen(appUiState, viewModel) AutoTunnelAdvancedScreen(appUiState, viewModel)
} }
composable<Route.WifiDetectionMethod> {
WifiDetectionMethodScreen(appUiState, viewModel)
}
composable<Route.Logs> { LogsScreen(appViewState, viewModel) } composable<Route.Logs> { LogsScreen(appViewState, viewModel) }
composable<Route.Config> { backStack -> composable<Route.Config> { backStack ->
val args = backStack.toRoute<Route.Config>() val args = backStack.toRoute<Route.Config>()
@@ -298,11 +302,10 @@ class MainActivity : AppCompatActivity() {
appUiState.tunnels appUiState.tunnels
.firstOrNull { it.id == args.id } .firstOrNull { it.id == args.id }
?.let { config -> ?.let { config ->
TunnelOptionsScreen(config, viewModel) TunnelOptionsScreen(config, viewModel, appViewState)
} }
} }
composable<Route.Lock> { PinLockScreen(viewModel) } composable<Route.Lock> { PinLockScreen(viewModel) }
composable<Route.Scanner> { ScannerScreen(viewModel) }
composable<Route.KillSwitch> { composable<Route.KillSwitch> {
KillSwitchScreen(appUiState, viewModel) KillSwitchScreen(appUiState, viewModel)
} }
@@ -14,9 +14,9 @@ import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
@@ -24,22 +24,10 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.distinctByKeys
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.*
import kotlinx.coroutines.Job import kotlinx.coroutines.flow.*
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint
@@ -15,12 +15,12 @@ import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotificati
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.AutoTunnelState import com.zaneschepke.wireguardautotunnel.domain.state.AutoTunnelState
import com.zaneschepke.wireguardautotunnel.domain.state.NetworkState import com.zaneschepke.wireguardautotunnel.domain.state.NetworkState
@@ -29,20 +29,8 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.*
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint
@@ -72,6 +60,8 @@ class AutoTunnelService : LifecycleService() {
private val binder = LocalBinder(this) private val binder = LocalBinder(this)
private var isServiceRunning = false
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
launchWatcherNotification() launchWatcherNotification()
@@ -90,6 +80,8 @@ class AutoTunnelService : LifecycleService() {
} }
fun start() { fun start() {
if (isServiceRunning) return
isServiceRunning = true
kotlin kotlin
.runCatching { .runCatching {
launchWatcherNotification() launchWatcherNotification()
@@ -102,6 +94,7 @@ class AutoTunnelService : LifecycleService() {
} }
fun stop() { fun stop() {
isServiceRunning = false
wakeLock?.let { if (it.isHeld) it.release() } wakeLock?.let { if (it.isHeld) it.release() }
stopSelf() stopSelf()
} }
@@ -261,18 +254,44 @@ class AutoTunnelService : LifecycleService() {
lifecycleScope.launch(ioDispatcher) { lifecycleScope.launch(ioDispatcher) {
Timber.i("Starting auto-tunnel network event watcher") Timber.i("Starting auto-tunnel network event watcher")
val settings = appDataRepository.get().settings.get() val settings = appDataRepository.get().settings.get()
Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds")
var reevaluationJob: Job? = null
autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState -> autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState ->
if (watcherState == defaultState) return@collect if (watcherState == defaultState) return@collect
Timber.d("New auto tunnel state emitted ${watcherState.networkState}") reevaluationJob?.cancel()
when (val event = watcherState.asAutoTunnelEvent()) { handleAutoTunnelEvent(watcherState)
is AutoTunnelEvent.Start ->
(event.tunnelConf ?: appDataRepository.get().getPrimaryOrFirstTunnel()) // schedule one-time re-evaluation
?.let { tunnelManager.startTunnel(it) } reevaluationJob = launch {
// TODO improve this to target specific tunnels to better support multi-tunnel delay(REEVALUATE_CHECK_DELAY)
is AutoTunnelEvent.Stop -> tunnelManager.stopTunnel() if (watcherState != defaultState) {
AutoTunnelEvent.DoNothing -> Timber.i("Auto-tunneling: no condition met") Timber.d("Re-evaluating auto-tunnel state..")
handleAutoTunnelEvent(watcherState)
}
} }
} }
} }
private suspend fun handleAutoTunnelEvent(watcherState: AutoTunnelState) {
Timber.i("Auto-tunnel settings: ${watcherState.settings.toAutoTunnelStateString()}")
Timber.i("Auto-tunnel network state: ${watcherState.networkState}")
when (
val event =
watcherState.asAutoTunnelEvent().also {
Timber.i("Auto-tunnel event: ${it.javaClass.simpleName}")
}
) {
is AutoTunnelEvent.Start ->
(event.tunnelConf ?: appDataRepository.get().getPrimaryOrFirstTunnel())?.let {
tunnelManager.startTunnel(it)
}
is AutoTunnelEvent.Stop -> tunnelManager.stopTunnel()
AutoTunnelEvent.DoNothing -> Timber.i("Auto-tunneling: nothing to do")
}
}
companion object {
const val REEVALUATE_CHECK_DELAY = 5_000L
}
} }
@@ -13,7 +13,7 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@@ -3,9 +3,9 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
@@ -114,16 +114,16 @@ abstract class BaseTunnel(
if (this@BaseTunnel is UserspaceTunnel) stopActiveTunnels() if (this@BaseTunnel is UserspaceTunnel) stopActiveTunnels()
tunMutex.withLock { tunMutex.withLock {
tunThreads[tunnelConf.id] = thread { tunThreads[tunnelConf.id] = thread {
runBlocking { try {
try { runBlocking {
Timber.d("Starting tunnel ${tunnelConf.id}...") Timber.d("Starting tunnel ${tunnelConf.id}...")
startTunnelInner(tunnelConf) startTunnelInner(tunnelConf)
Timber.d("Started complete for tunnel ${tunnelConf.name}...") Timber.d("Started complete for tunnel ${tunnelConf.name}...")
} catch (e: InterruptedException) {
Timber.w(
"Tunnel start has been interrupted as ${tunnelConf.name} failed to start"
)
} }
} catch (e: InterruptedException) {
Timber.w(
"Tunnel start has been interrupted as ${tunnelConf.name} failed to start"
)
} }
} }
} }
@@ -1,7 +1,7 @@
package com.zaneschepke.wireguardautotunnel.core.tunnel package com.zaneschepke.wireguardautotunnel.core.tunnel
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -5,9 +5,9 @@ import com.wireguard.android.backend.BackendException
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.domain.state.WireGuardStatistics import com.zaneschepke.wireguardautotunnel.domain.state.WireGuardStatistics
@@ -4,10 +4,10 @@ import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.di.Kernel import com.zaneschepke.wireguardautotunnel.di.Kernel
import com.zaneschepke.wireguardautotunnel.di.Userspace import com.zaneschepke.wireguardautotunnel.di.Userspace
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -1,9 +1,9 @@
package com.zaneschepke.wireguardautotunnel.core.tunnel package com.zaneschepke.wireguardautotunnel.core.tunnel
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -2,9 +2,9 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
@@ -8,12 +8,12 @@ import androidx.room.TypeConverters
import androidx.room.migration.AutoMigrationSpec import androidx.room.migration.AutoMigrationSpec
import com.zaneschepke.wireguardautotunnel.data.dao.SettingsDao import com.zaneschepke.wireguardautotunnel.data.dao.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.dao.TunnelConfigDao import com.zaneschepke.wireguardautotunnel.data.dao.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.data.model.Settings import com.zaneschepke.wireguardautotunnel.data.entity.Settings
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
@Database( @Database(
entities = [Settings::class, TunnelConfig::class], entities = [Settings::class, TunnelConfig::class],
version = 16, version = 17,
autoMigrations = autoMigrations =
[ [
AutoMigration(from = 1, to = 2), AutoMigration(from = 1, to = 2),
@@ -31,10 +31,11 @@ import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
AutoMigration(from = 13, to = 14), AutoMigration(from = 13, to = 14),
AutoMigration(from = 14, to = 15), AutoMigration(from = 14, to = 15),
AutoMigration(from = 15, to = 16), AutoMigration(from = 15, to = 16),
AutoMigration(from = 16, to = 17, spec = WifiDetectionMigration::class),
], ],
exportSchema = true, exportSchema = true,
) )
@TypeConverters(DatabaseListConverters::class) @TypeConverters(DatabaseConverters::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun settingDao(): SettingsDao abstract fun settingDao(): SettingsDao
@@ -47,3 +48,6 @@ class RemoveLegacySettingColumnsMigration : AutoMigrationSpec
@DeleteColumn(tableName = "Settings", columnName = "is_auto_tunnel_paused") @DeleteColumn(tableName = "Settings", columnName = "is_auto_tunnel_paused")
class RemoveTunnelPauseMigration : AutoMigrationSpec class RemoveTunnelPauseMigration : AutoMigrationSpec
@DeleteColumn(tableName = "Settings", columnName = "is_wifi_by_shell_enabled")
class WifiDetectionMigration : AutoMigrationSpec
@@ -1,9 +1,10 @@
package com.zaneschepke.wireguardautotunnel.data package com.zaneschepke.wireguardautotunnel.data
import androidx.room.TypeConverter import androidx.room.TypeConverter
import com.zaneschepke.wireguardautotunnel.data.entity.Settings
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
class DatabaseListConverters { class DatabaseConverters {
@TypeConverter @TypeConverter
fun listToString(value: MutableList<String>): String { fun listToString(value: MutableList<String>): String {
return Json.encodeToString(value) return Json.encodeToString(value)
@@ -20,4 +21,10 @@ class DatabaseListConverters {
Json.decodeFromString<MutableList<String>>(json) Json.decodeFromString<MutableList<String>>(json)
} }
} }
@TypeConverter fun fromStatus(status: Settings.WifiDetectionMethod): Int = status.value
@TypeConverter
fun toStatus(value: Int): Settings.WifiDetectionMethod =
Settings.WifiDetectionMethod.fromValue(value)
} }
@@ -5,7 +5,7 @@ import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.data.model.Settings import com.zaneschepke.wireguardautotunnel.data.entity.Settings
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@@ -5,7 +5,7 @@ import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.data.model package com.zaneschepke.wireguardautotunnel.data.entity
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -0,0 +1,24 @@
package com.zaneschepke.wireguardautotunnel.data.entity
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
data class GeneralState(
val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
val expandedTunnelIds: List<Int> = emptyList(),
val isLocalLogsEnabled: Boolean = IS_LOGS_ENABLED_DEFAULT,
val isRemoteControlEnabled: Boolean = IS_REMOTE_CONTROL_ENABLED,
val remoteKey: String? = null,
val locale: String? = null,
val theme: Theme = Theme.AUTOMATIC,
) {
companion object {
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
const val PIN_LOCK_ENABLED_DEFAULT = false
const val IS_LOGS_ENABLED_DEFAULT = false
const val IS_REMOTE_CONTROL_ENABLED = false
}
}
@@ -0,0 +1,12 @@
package com.zaneschepke.wireguardautotunnel.data.entity
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GitHubRelease(
@SerialName("tag_name") val tagName: String,
val name: String?,
val body: String?,
val assets: List<Asset>,
)
@@ -1,9 +1,8 @@
package com.zaneschepke.wireguardautotunnel.data.model package com.zaneschepke.wireguardautotunnel.data.entity
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
@Entity @Entity
data class Settings( data class Settings(
@@ -32,8 +31,6 @@ data class Settings(
val isAmneziaEnabled: Boolean = false, val isAmneziaEnabled: Boolean = false,
@ColumnInfo(name = "is_wildcards_enabled", defaultValue = "false") @ColumnInfo(name = "is_wildcards_enabled", defaultValue = "false")
val isWildcardsEnabled: Boolean = false, val isWildcardsEnabled: Boolean = false,
@ColumnInfo(name = "is_wifi_by_shell_enabled", defaultValue = "false")
val isWifiNameByShellEnabled: Boolean = false,
@ColumnInfo(name = "is_stop_on_no_internet_enabled", defaultValue = "false") @ColumnInfo(name = "is_stop_on_no_internet_enabled", defaultValue = "false")
val isStopOnNoInternetEnabled: Boolean = false, val isStopOnNoInternetEnabled: Boolean = false,
@ColumnInfo(name = "is_vpn_kill_switch_enabled", defaultValue = "false") @ColumnInfo(name = "is_vpn_kill_switch_enabled", defaultValue = "false")
@@ -46,61 +43,23 @@ data class Settings(
val debounceDelaySeconds: Int = 3, val debounceDelaySeconds: Int = 3,
@ColumnInfo(name = "is_disable_kill_switch_on_trusted_enabled", defaultValue = "false") @ColumnInfo(name = "is_disable_kill_switch_on_trusted_enabled", defaultValue = "false")
val isDisableKillSwitchOnTrustedEnabled: Boolean = false, val isDisableKillSwitchOnTrustedEnabled: Boolean = false,
@ColumnInfo(name = "is_tunnel_on_unsecure_enabled", defaultValue = "false")
val isTunnelOnUnsecureEnabled: Boolean = false,
@ColumnInfo(name = "split_tunnel_apps", defaultValue = "")
val splitTunnelApps: MutableList<String> = mutableListOf(),
@ColumnInfo(name = "wifi_detection_method", defaultValue = "0")
val wifiDetectionMethod: WifiDetectionMethod = WifiDetectionMethod.fromValue(0),
) { ) {
fun toAppSettings(): AppSettings { enum class WifiDetectionMethod(val value: Int) {
return AppSettings( DEFAULT(0),
id, LEGACY(1),
isAutoTunnelEnabled, ROOT(2),
isTunnelOnMobileDataEnabled, SHIZUKU(3);
trustedNetworkSSIDs,
isAlwaysOnVpnEnabled,
isTunnelOnEthernetEnabled,
isShortcutsEnabled,
isTunnelOnWifiEnabled,
isKernelEnabled,
isRestoreOnBootEnabled,
isMultiTunnelEnabled,
isPingEnabled,
isAmneziaEnabled,
isWildcardsEnabled,
isWifiNameByShellEnabled,
isStopOnNoInternetEnabled,
isVpnKillSwitchEnabled,
isKernelKillSwitchEnabled,
isLanOnKillSwitchEnabled,
debounceDelaySeconds,
isDisableKillSwitchOnTrustedEnabled,
)
}
companion object { companion object {
fun from(appSettings: AppSettings): Settings { fun fromValue(value: Int): WifiDetectionMethod =
return with(appSettings) { entries.find { it.value == value } ?: DEFAULT
Settings(
id,
isAutoTunnelEnabled,
isTunnelOnMobileDataEnabled,
trustedNetworkSSIDs.toMutableList(),
isAlwaysOnVpnEnabled,
isTunnelOnEthernetEnabled,
isShortcutsEnabled,
isTunnelOnWifiEnabled,
isKernelEnabled,
isRestoreOnBootEnabled,
isMultiTunnelEnabled,
isPingEnabled,
isAmneziaEnabled,
isWildcardsEnabled,
isWifiNameByShellEnabled,
isStopOnNoInternetEnabled,
isVpnKillSwitchEnabled,
isKernelKillSwitchEnabled,
isLanOnKillSwitchEnabled,
debounceDelaySeconds,
isDisableKillSwitchOnTrustedEnabled,
)
}
} }
} }
} }
@@ -1,10 +1,9 @@
package com.zaneschepke.wireguardautotunnel.data.model package com.zaneschepke.wireguardautotunnel.data.entity
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
@Entity(indices = [Index(value = ["name"], unique = true)]) @Entity(indices = [Index(value = ["name"], unique = true)])
data class TunnelConfig( data class TunnelConfig(
@@ -30,48 +29,7 @@ data class TunnelConfig(
var isIpv4Preferred: Boolean = true, var isIpv4Preferred: Boolean = true,
) { ) {
fun toTunnel(): TunnelConf {
return TunnelConf(
id,
name,
wgQuick,
tunnelNetworks,
isMobileDataTunnel,
isPrimaryTunnel,
amQuick,
isActive,
isPingEnabled,
pingInterval,
pingCooldown,
pingIp,
isEthernetTunnel,
isIpv4Preferred,
)
}
companion object { companion object {
const val AM_QUICK_DEFAULT = "" const val AM_QUICK_DEFAULT = ""
fun from(tunnelConf: TunnelConf): TunnelConfig {
return with(tunnelConf) {
return TunnelConfig(
id,
tunName,
wgQuick,
tunnelNetworks.toMutableList(),
isMobileDataTunnel,
isPrimaryTunnel,
amQuick,
isActive,
isPingEnabled,
pingInterval,
pingCooldown,
pingIp,
isEthernetTunnel,
isIpv4Preferred,
)
}
}
} }
} }
@@ -0,0 +1,37 @@
package com.zaneschepke.wireguardautotunnel.data.mapper
import com.zaneschepke.wireguardautotunnel.data.entity.GeneralState
import com.zaneschepke.wireguardautotunnel.domain.model.AppState
object GeneralStateMapper {
fun toAppState(generalState: GeneralState): AppState =
with(generalState) {
AppState(
isLocationDisclosureShown,
isBatteryOptimizationDisableShown,
isPinLockEnabled,
expandedTunnelIds,
isLocalLogsEnabled,
isRemoteControlEnabled,
remoteKey,
locale,
theme,
)
}
fun toGeneralState(appState: AppState): GeneralState {
return with(appState) {
GeneralState(
isLocationDisclosureShown,
isBatteryOptimizationDisableShown,
isPinLockEnabled,
expandedTunnelIds,
isLocalLogsEnabled,
isRemoteControlEnabled,
remoteKey,
locale,
theme,
)
}
}
}
@@ -0,0 +1,20 @@
package com.zaneschepke.wireguardautotunnel.data.mapper
import com.zaneschepke.wireguardautotunnel.data.entity.GitHubRelease
import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
import kotlin.collections.firstOrNull
object GitHubReleaseMapper {
fun toAppUpdate(gitHubRelease: GitHubRelease, newVersion: String): AppUpdate {
with(gitHubRelease) {
val apkAsset = assets.firstOrNull { it.name.endsWith(".apk") }
return AppUpdate(
version = newVersion,
title = name ?: "Update $tagName",
releaseNotes = body ?: "No release notes provided",
apkUrl = apkAsset?.browserDownloadUrl,
apkFileName = apkAsset?.name,
)
}
}
}
@@ -0,0 +1,67 @@
package com.zaneschepke.wireguardautotunnel.data.mapper
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
import com.zaneschepke.wireguardautotunnel.data.entity.Settings
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
object SettingsMapper {
fun toAppSettings(settings: Settings): AppSettings {
return AppSettings(
id = settings.id,
isAutoTunnelEnabled = settings.isAutoTunnelEnabled,
isTunnelOnMobileDataEnabled = settings.isTunnelOnMobileDataEnabled,
trustedNetworkSSIDs = settings.trustedNetworkSSIDs,
isAlwaysOnVpnEnabled = settings.isAlwaysOnVpnEnabled,
isTunnelOnEthernetEnabled = settings.isTunnelOnEthernetEnabled,
isShortcutsEnabled = settings.isShortcutsEnabled,
isTunnelOnWifiEnabled = settings.isTunnelOnWifiEnabled,
isKernelEnabled = settings.isKernelEnabled,
isRestoreOnBootEnabled = settings.isRestoreOnBootEnabled,
isMultiTunnelEnabled = settings.isMultiTunnelEnabled,
isPingEnabled = settings.isPingEnabled,
isAmneziaEnabled = settings.isAmneziaEnabled,
isWildcardsEnabled = settings.isWildcardsEnabled,
isStopOnNoInternetEnabled = settings.isStopOnNoInternetEnabled,
isVpnKillSwitchEnabled = settings.isVpnKillSwitchEnabled,
isKernelKillSwitchEnabled = settings.isKernelKillSwitchEnabled,
isLanOnKillSwitchEnabled = settings.isLanOnKillSwitchEnabled,
debounceDelaySeconds = settings.debounceDelaySeconds,
isDisableKillSwitchOnTrustedEnabled = settings.isDisableKillSwitchOnTrustedEnabled,
isTunnelOnUnsecureEnabled = settings.isTunnelOnUnsecureEnabled,
splitTunnelApps = settings.splitTunnelApps,
wifiDetectionMethod =
AndroidNetworkMonitor.WifiDetectionMethod.fromValue(
settings.wifiDetectionMethod.value
),
)
}
fun toSettings(appSettings: AppSettings): Settings {
return Settings(
id = appSettings.id,
isAutoTunnelEnabled = appSettings.isAutoTunnelEnabled,
isTunnelOnMobileDataEnabled = appSettings.isTunnelOnMobileDataEnabled,
trustedNetworkSSIDs = appSettings.trustedNetworkSSIDs.toMutableList(),
isAlwaysOnVpnEnabled = appSettings.isAlwaysOnVpnEnabled,
isTunnelOnEthernetEnabled = appSettings.isTunnelOnEthernetEnabled,
isShortcutsEnabled = appSettings.isShortcutsEnabled,
isTunnelOnWifiEnabled = appSettings.isTunnelOnWifiEnabled,
isKernelEnabled = appSettings.isKernelEnabled,
isRestoreOnBootEnabled = appSettings.isRestoreOnBootEnabled,
isMultiTunnelEnabled = appSettings.isMultiTunnelEnabled,
isPingEnabled = appSettings.isPingEnabled,
isAmneziaEnabled = appSettings.isAmneziaEnabled,
isWildcardsEnabled = appSettings.isWildcardsEnabled,
isStopOnNoInternetEnabled = appSettings.isStopOnNoInternetEnabled,
isVpnKillSwitchEnabled = appSettings.isVpnKillSwitchEnabled,
isKernelKillSwitchEnabled = appSettings.isKernelKillSwitchEnabled,
isLanOnKillSwitchEnabled = appSettings.isLanOnKillSwitchEnabled,
debounceDelaySeconds = appSettings.debounceDelaySeconds,
isDisableKillSwitchOnTrustedEnabled = appSettings.isDisableKillSwitchOnTrustedEnabled,
isTunnelOnUnsecureEnabled = appSettings.isTunnelOnUnsecureEnabled,
splitTunnelApps = appSettings.splitTunnelApps.toMutableList(),
wifiDetectionMethod =
Settings.WifiDetectionMethod.fromValue(appSettings.wifiDetectionMethod.value),
)
}
}
@@ -0,0 +1,48 @@
package com.zaneschepke.wireguardautotunnel.data.mapper
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
object TunnelConfigMapper {
fun toTunnelConf(tunnelConfig: TunnelConfig): TunnelConf {
return with(tunnelConfig) {
TunnelConf(
id,
name,
wgQuick,
tunnelNetworks,
isMobileDataTunnel,
isPrimaryTunnel,
amQuick,
isActive,
isPingEnabled,
pingInterval,
pingCooldown,
pingIp,
isEthernetTunnel,
isIpv4Preferred,
)
}
}
fun toTunnelConfig(tunnelConf: TunnelConf): TunnelConfig {
return with(tunnelConf) {
TunnelConfig(
id,
tunName,
wgQuick,
tunnelNetworks.toMutableList(),
isMobileDataTunnel,
isPrimaryTunnel,
amQuick,
isActive,
isPingEnabled,
pingInterval,
pingCooldown,
pingIp,
isEthernetTunnel,
isIpv4Preferred,
)
}
}
}
@@ -1,54 +0,0 @@
package com.zaneschepke.wireguardautotunnel.data.model
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
data class GeneralState(
val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
val expandedTunnelIds: List<Int> = emptyList(),
val isLocalLogsEnabled: Boolean = IS_LOGS_ENABLED_DEFAULT,
val isRemoteControlEnabled: Boolean = IS_REMOTE_CONTROL_ENABLED,
val remoteKey: String? = null,
val locale: String? = null,
val theme: Theme = Theme.AUTOMATIC,
) {
fun toAppState(): AppState =
AppState(
isLocationDisclosureShown,
isBatteryOptimizationDisableShown,
isPinLockEnabled,
expandedTunnelIds,
isLocalLogsEnabled,
isRemoteControlEnabled,
remoteKey,
locale,
theme,
)
companion object {
fun from(appState: AppState): GeneralState {
return with(appState) {
GeneralState(
isLocationDisclosureShown,
isBatteryOptimizationDisableShown,
isPinLockEnabled,
expandedTunnelIds,
isLocalLogsEnabled,
isRemoteControlEnabled,
remoteKey,
locale,
theme,
)
}
}
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
const val PIN_LOCK_ENABLED_DEFAULT = false
const val IS_LOGS_ENABLED_DEFAULT = false
const val IS_REMOTE_CONTROL_ENABLED = false
}
}
@@ -1,24 +0,0 @@
package com.zaneschepke.wireguardautotunnel.data.model
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GitHubRelease(
@SerialName("tag_name") val tagName: String,
val name: String?,
val body: String?,
val assets: List<Asset>,
) {
fun toAppUpdate(): AppUpdate {
val apkAsset = assets.firstOrNull { it.name.endsWith(".apk") }
return AppUpdate(
version = tagName.removePrefix("v"),
title = name ?: "Update $tagName",
releaseNotes = body ?: "No release notes provided",
apkUrl = apkAsset?.browserDownloadUrl,
apkFileName = apkAsset?.name,
)
}
}
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.data.network package com.zaneschepke.wireguardautotunnel.data.network
import com.zaneschepke.wireguardautotunnel.data.model.GitHubRelease import com.zaneschepke.wireguardautotunnel.data.entity.GitHubRelease
interface GitHubApi { interface GitHubApi {
suspend fun getLatestRelease(owner: String, repo: String): Result<GitHubRelease> suspend fun getLatestRelease(owner: String, repo: String): Result<GitHubRelease>
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.data.network package com.zaneschepke.wireguardautotunnel.data.network
import com.zaneschepke.wireguardautotunnel.data.model.GitHubRelease import com.zaneschepke.wireguardautotunnel.data.entity.GitHubRelease
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.plugins.ClientRequestException import io.ktor.client.plugins.ClientRequestException
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.data.repository package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
@@ -1,8 +1,9 @@
package com.zaneschepke.wireguardautotunnel.data.repository package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.DataStoreManager import com.zaneschepke.wireguardautotunnel.data.DataStoreManager
import com.zaneschepke.wireguardautotunnel.data.model.GeneralState import com.zaneschepke.wireguardautotunnel.data.entity.GeneralState
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState import com.zaneschepke.wireguardautotunnel.data.mapper.GeneralStateMapper
import com.zaneschepke.wireguardautotunnel.domain.model.AppState
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -153,5 +154,5 @@ class DataStoreAppStateRepository(private val dataStoreManager: DataStoreManager
} }
} ?: GeneralState() } ?: GeneralState()
} }
.map { it.toAppState() } .map(GeneralStateMapper::toAppState)
} }
@@ -2,10 +2,12 @@ package com.zaneschepke.wireguardautotunnel.data.repository
import android.content.Context import android.content.Context
import com.zaneschepke.wireguardautotunnel.BuildConfig import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.data.mapper.GitHubReleaseMapper
import com.zaneschepke.wireguardautotunnel.data.network.GitHubApi import com.zaneschepke.wireguardautotunnel.data.network.GitHubApi
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.NumberUtils import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
@@ -30,24 +32,30 @@ class GitHubUpdateRepository(
override suspend fun checkForUpdate(currentVersion: String): Result<AppUpdate?> = override suspend fun checkForUpdate(currentVersion: String): Result<AppUpdate?> =
withContext(ioDispatcher) { withContext(ioDispatcher) {
Timber.i("Checking for update") Timber.i("Checking for update")
val isNightly = BuildConfig.VERSION_NAME.contains("nightly")
val release = val release =
if (BuildConfig.VERSION_NAME.contains("nightly")) { if (isNightly) {
gitHubApi.getNightlyRelease(githubOwner, githubRepo) gitHubApi.getNightlyRelease(githubOwner, githubRepo).onFailure(Timber::e)
} else { } else {
gitHubApi.getLatestRelease(githubOwner, githubRepo) gitHubApi.getLatestRelease(githubOwner, githubRepo).onFailure(Timber::e)
} }
release.map { release -> release.map { release ->
val apkAsset = val apkAsset =
release.assets.find { asset -> release.assets.find { asset ->
asset.name.startsWith("wgtunnel-full-v") && asset.name.endsWith(".apk") asset.name.startsWith("wgtunnel-${Constants.STANDALONE_FLAVOR}-v") &&
asset.name.endsWith(".apk")
} }
val newVersion = val newVersion =
apkAsset?.name?.removePrefix("wgtunnel-full-v")?.removeSuffix(".apk") apkAsset
?: return@map null ?.name
?.removePrefix("wgtunnel-${Constants.STANDALONE_FLAVOR}-v")
?.removeSuffix(".apk") ?: return@map null
Timber.i("Latest version: $newVersion, current version: $currentVersion") Timber.i("Latest version: $newVersion, current version: $currentVersion")
if (isNightly && newVersion != currentVersion)
return@map GitHubReleaseMapper.toAppUpdate(release, newVersion)
if (NumberUtils.compareVersions(newVersion, currentVersion) > 0) { if (NumberUtils.compareVersions(newVersion, currentVersion) > 0) {
release.toAppUpdate() GitHubReleaseMapper.toAppUpdate(release, newVersion)
} else { } else {
null null
} }
@@ -1,9 +1,10 @@
package com.zaneschepke.wireguardautotunnel.data.repository package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.dao.SettingsDao import com.zaneschepke.wireguardautotunnel.data.dao.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.model.Settings import com.zaneschepke.wireguardautotunnel.data.entity.Settings
import com.zaneschepke.wireguardautotunnel.data.mapper.SettingsMapper
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
@@ -16,15 +17,15 @@ class RoomSettingsRepository(
) : AppSettingRepository { ) : AppSettingRepository {
override suspend fun save(appSettings: AppSettings) { override suspend fun save(appSettings: AppSettings) {
withContext(ioDispatcher) { settingsDoa.save(Settings.from(appSettings)) } withContext(ioDispatcher) { settingsDoa.save(SettingsMapper.toSettings(appSettings)) }
} }
override val flow = override val flow =
settingsDoa.getSettingsFlow().flowOn(ioDispatcher).map { it.toAppSettings() } settingsDoa.getSettingsFlow().flowOn(ioDispatcher).map(SettingsMapper::toAppSettings)
override suspend fun get(): AppSettings { override suspend fun get(): AppSettings {
return withContext(ioDispatcher) { return withContext(ioDispatcher) {
(settingsDoa.getAll().firstOrNull() ?: Settings()).toAppSettings() SettingsMapper.toAppSettings(settingsDoa.getAll().firstOrNull() ?: Settings())
} }
} }
} }
@@ -1,9 +1,9 @@
package com.zaneschepke.wireguardautotunnel.data.repository package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.dao.TunnelConfigDao import com.zaneschepke.wireguardautotunnel.data.dao.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.mapper.TunnelConfigMapper
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@@ -17,19 +17,25 @@ class RoomTunnelRepository(
) : TunnelRepository { ) : TunnelRepository {
override val flow = override val flow =
tunnelConfigDao.getAllFlow().flowOn(ioDispatcher).map { it.map { it.toTunnel() } } tunnelConfigDao.getAllFlow().flowOn(ioDispatcher).map {
it.map(TunnelConfigMapper::toTunnelConf)
}
override suspend fun getAll(): Tunnels { override suspend fun getAll(): Tunnels {
return withContext(ioDispatcher) { tunnelConfigDao.getAll().map { it.toTunnel() } } return withContext(ioDispatcher) {
tunnelConfigDao.getAll().map(TunnelConfigMapper::toTunnelConf)
}
} }
override suspend fun save(tunnelConf: TunnelConf) { override suspend fun save(tunnelConf: TunnelConf) {
withContext(ioDispatcher) { tunnelConfigDao.save(TunnelConfig.from(tunnelConf)) } withContext(ioDispatcher) {
tunnelConfigDao.save(TunnelConfigMapper.toTunnelConfig(tunnelConf))
}
} }
override suspend fun saveAll(tunnelConfList: List<TunnelConf>) { override suspend fun saveAll(tunnelConfList: List<TunnelConf>) {
withContext(ioDispatcher) { withContext(ioDispatcher) {
tunnelConfigDao.saveAll(tunnelConfList.map(TunnelConfig::from)) tunnelConfigDao.saveAll(tunnelConfList.map(TunnelConfigMapper::toTunnelConfig))
} }
} }
@@ -55,15 +61,21 @@ class RoomTunnelRepository(
} }
override suspend fun delete(tunnelConf: TunnelConf) { override suspend fun delete(tunnelConf: TunnelConf) {
withContext(ioDispatcher) { tunnelConfigDao.delete(TunnelConfig.from(tunnelConf)) } withContext(ioDispatcher) {
tunnelConfigDao.delete(TunnelConfigMapper.toTunnelConfig(tunnelConf))
}
} }
override suspend fun getById(id: Int): TunnelConf? { override suspend fun getById(id: Int): TunnelConf? {
return withContext(ioDispatcher) { tunnelConfigDao.getById(id.toLong())?.toTunnel() } return withContext(ioDispatcher) {
tunnelConfigDao.getById(id.toLong())?.let(TunnelConfigMapper::toTunnelConf)
}
} }
override suspend fun getActive(): Tunnels { override suspend fun getActive(): Tunnels {
return withContext(ioDispatcher) { tunnelConfigDao.getActive().map { it.toTunnel() } } return withContext(ioDispatcher) {
tunnelConfigDao.getActive().map(TunnelConfigMapper::toTunnelConf)
}
} }
override suspend fun count(): Int { override suspend fun count(): Int {
@@ -71,22 +83,26 @@ class RoomTunnelRepository(
} }
override suspend fun findByTunnelName(name: String): TunnelConf? { override suspend fun findByTunnelName(name: String): TunnelConf? {
return withContext(ioDispatcher) { tunnelConfigDao.getByName(name)?.toTunnel() } return withContext(ioDispatcher) {
tunnelConfigDao.getByName(name)?.let(TunnelConfigMapper::toTunnelConf)
}
} }
override suspend fun findByTunnelNetworksName(name: String): Tunnels { override suspend fun findByTunnelNetworksName(name: String): Tunnels {
return withContext(ioDispatcher) { return withContext(ioDispatcher) {
tunnelConfigDao.findByTunnelNetworkName(name).map { it.toTunnel() } tunnelConfigDao.findByTunnelNetworkName(name).map(TunnelConfigMapper::toTunnelConf)
} }
} }
override suspend fun findByMobileDataTunnel(): Tunnels { override suspend fun findByMobileDataTunnel(): Tunnels {
return withContext(ioDispatcher) { return withContext(ioDispatcher) {
tunnelConfigDao.findByMobileDataTunnel().map { it.toTunnel() } tunnelConfigDao.findByMobileDataTunnel().map(TunnelConfigMapper::toTunnelConf)
} }
} }
override suspend fun findPrimary(): Tunnels { override suspend fun findPrimary(): Tunnels {
return withContext(ioDispatcher) { tunnelConfigDao.findByPrimary().map { it.toTunnel() } } return withContext(ioDispatcher) {
tunnelConfigDao.findByPrimary().map(TunnelConfigMapper::toTunnelConf)
}
} }
} }
@@ -21,7 +21,9 @@ import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton import javax.inject.Singleton
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map
import org.amnezia.awg.backend.Backend import org.amnezia.awg.backend.Backend
import org.amnezia.awg.backend.GoBackend import org.amnezia.awg.backend.GoBackend
import org.amnezia.awg.backend.RootTunnelActionHandler import org.amnezia.awg.backend.RootTunnelActionHandler
@@ -112,10 +114,23 @@ class TunnelModule {
fun provideNetworkMonitor( fun provideNetworkMonitor(
@ApplicationContext context: Context, @ApplicationContext context: Context,
settingsRepository: AppSettingRepository, settingsRepository: AppSettingRepository,
@ApplicationScope applicationScope: CoroutineScope,
@AppShell appShell: RootShell,
): NetworkMonitor { ): NetworkMonitor {
return AndroidNetworkMonitor(context) { return AndroidNetworkMonitor(
runBlocking { settingsRepository.get().isWifiNameByShellEnabled } context,
} object : AndroidNetworkMonitor.ConfigurationListener {
override val detectionMethod: Flow<AndroidNetworkMonitor.WifiDetectionMethod>
get() =
settingsRepository.flow
.distinctUntilChangedBy { it.wifiDetectionMethod }
.map { it.wifiDetectionMethod }
override val rootShell: RootShell
get() = appShell
},
applicationScope,
)
} }
@Singleton @Singleton
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.domain.enums package com.zaneschepke.wireguardautotunnel.domain.enums
enum class ConfigType { enum class ConfigType {
AMNEZIA, AM,
WG, WG,
} }
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.domain.events package com.zaneschepke.wireguardautotunnel.domain.events
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
sealed class AutoTunnelEvent { sealed class AutoTunnelEvent {
data class Start(val tunnelConf: TunnelConf? = null) : AutoTunnelEvent() data class Start(val tunnelConf: TunnelConf? = null) : AutoTunnelEvent()
@@ -1,4 +1,6 @@
package com.zaneschepke.wireguardautotunnel.domain.entity package com.zaneschepke.wireguardautotunnel.domain.model
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
data class AppSettings( data class AppSettings(
val id: Int = 0, val id: Int = 0,
@@ -15,15 +17,30 @@ data class AppSettings(
val isPingEnabled: Boolean = false, val isPingEnabled: Boolean = false,
val isAmneziaEnabled: Boolean = false, val isAmneziaEnabled: Boolean = false,
val isWildcardsEnabled: Boolean = false, val isWildcardsEnabled: Boolean = false,
val isWifiNameByShellEnabled: Boolean = false,
val isStopOnNoInternetEnabled: Boolean = false, val isStopOnNoInternetEnabled: Boolean = false,
val isVpnKillSwitchEnabled: Boolean = false, val isVpnKillSwitchEnabled: Boolean = false,
val isKernelKillSwitchEnabled: Boolean = false, val isKernelKillSwitchEnabled: Boolean = false,
val isLanOnKillSwitchEnabled: Boolean = false, val isLanOnKillSwitchEnabled: Boolean = false,
val debounceDelaySeconds: Int = 3, val debounceDelaySeconds: Int = 3,
val isDisableKillSwitchOnTrustedEnabled: Boolean = false, val isDisableKillSwitchOnTrustedEnabled: Boolean = false,
val isTunnelOnUnsecureEnabled: Boolean = false,
val splitTunnelApps: List<String> = emptyList(),
val wifiDetectionMethod: AndroidNetworkMonitor.WifiDetectionMethod =
AndroidNetworkMonitor.WifiDetectionMethod.DEFAULT,
) { ) {
fun debounceDelayMillis(): Long { fun debounceDelayMillis(): Long {
return debounceDelaySeconds * 1000L return debounceDelaySeconds * 1000L
} }
fun toAutoTunnelStateString(): String {
return """
TunnelOnWifi: $isTunnelOnWifiEnabled
TunnelOnMobileData: $isTunnelOnMobileDataEnabled
TunnelOnEthernet: $isTunnelOnEthernetEnabled
Wildcards: $isWildcardsEnabled
StopOnNoInternet: $isStopOnNoInternetEnabled
Trusted Networks: $trustedNetworkSSIDs
"""
.trimIndent()
}
} }
@@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.domain.entity package com.zaneschepke.wireguardautotunnel.domain.model
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
@@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.domain.entity package com.zaneschepke.wireguardautotunnel.domain.model
data class AppUpdate( data class AppUpdate(
val version: String, val version: String,
@@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.domain.entity package com.zaneschepke.wireguardautotunnel.domain.model
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.wireguard.config.Config import com.wireguard.config.Config
@@ -26,7 +26,6 @@ data class TunnelConf(
val pingIp: String? = null, val pingIp: String? = null,
val isEthernetTunnel: Boolean = false, val isEthernetTunnel: Boolean = false,
val isIpv4Preferred: Boolean = true, val isIpv4Preferred: Boolean = true,
val useCache: Boolean = false,
@Transient private var stateChangeCallback: ((Any) -> Unit)? = null, @Transient private var stateChangeCallback: ((Any) -> Unit)? = null,
) : Tunnel, org.amnezia.awg.backend.Tunnel { ) : Tunnel, org.amnezia.awg.backend.Tunnel {
@@ -111,8 +110,6 @@ data class TunnelConf(
override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred
override fun useCache(): Boolean = useCache
override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) { override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) {
stateChangeCallback?.invoke(newState) stateChangeCallback?.invoke(newState)
} }
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.domain.repository package com.zaneschepke.wireguardautotunnel.domain.repository
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
interface AppDataRepository { interface AppDataRepository {
suspend fun getPrimaryOrFirstTunnel(): TunnelConf? suspend fun getPrimaryOrFirstTunnel(): TunnelConf?
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.domain.repository package com.zaneschepke.wireguardautotunnel.domain.repository
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface AppSettingRepository { interface AppSettingRepository {
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.domain.repository package com.zaneschepke.wireguardautotunnel.domain.repository
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState import com.zaneschepke.wireguardautotunnel.domain.model.AppState
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.domain.repository package com.zaneschepke.wireguardautotunnel.domain.repository
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.domain.repository package com.zaneschepke.wireguardautotunnel.domain.repository
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
import java.io.File import java.io.File
interface UpdateRepository { interface UpdateRepository {
@@ -3,10 +3,10 @@ package com.zaneschepke.wireguardautotunnel.domain.state
import com.zaneschepke.wireguardautotunnel.core.tunnel.allDown import com.zaneschepke.wireguardautotunnel.core.tunnel.allDown
import com.zaneschepke.wireguardautotunnel.core.tunnel.hasActive import com.zaneschepke.wireguardautotunnel.core.tunnel.hasActive
import com.zaneschepke.wireguardautotunnel.core.tunnel.isUp import com.zaneschepke.wireguardautotunnel.core.tunnel.isUp
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
data class AutoTunnelState( data class AutoTunnelState(
@@ -16,6 +16,7 @@ data class AutoTunnelState(
val tunnels: List<TunnelConf> = emptyList(), val tunnels: List<TunnelConf> = emptyList(),
) { ) {
// also need to check for Wi-Fi state as there is some overlap when they are both connected
private fun isMobileDataActive(): Boolean { private fun isMobileDataActive(): Boolean {
return !networkState.isEthernetConnected && return !networkState.isEthernetConnected &&
!networkState.isWifiConnected && !networkState.isWifiConnected &&
@@ -50,6 +51,7 @@ data class AutoTunnelState(
return getTunnelWithMatchingTunnelNetwork() ?: tunnels.firstOrNull { it.isPrimaryTunnel } return getTunnelWithMatchingTunnelNetwork() ?: tunnels.firstOrNull { it.isPrimaryTunnel }
} }
// ignore cellular state as there is overlap where it may still be active, but not prioritized
private fun isWifiActive(): Boolean { private fun isWifiActive(): Boolean {
return !networkState.isEthernetConnected && networkState.isWifiConnected return !networkState.isEthernetConnected && networkState.isWifiConnected
} }
@@ -13,6 +13,8 @@ sealed class Route {
@Serializable data object AutoTunnelAdvanced : Route() @Serializable data object AutoTunnelAdvanced : Route()
@Serializable data object WifiDetectionMethod : Route()
@Serializable data object LocationDisclosure : Route() @Serializable data object LocationDisclosure : Route()
@Serializable data object Appearance : Route() @Serializable data object Appearance : Route()
@@ -29,8 +31,6 @@ sealed class Route {
@Serializable data object Lock : Route() @Serializable data object Lock : Route()
@Serializable data object Scanner : Route()
@Serializable data object License : Route() @Serializable data object License : Route()
@Serializable data class Config(val id: Int) : Route() @Serializable data class Config(val id: Int) : Route()
@@ -73,7 +73,7 @@ fun IconSurfaceButton(
else MaterialTheme.colorScheme.onSurface, else MaterialTheme.colorScheme.onSurface,
) )
} }
Column { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(title, style = MaterialTheme.typography.titleMedium) Text(title, style = MaterialTheme.typography.titleMedium)
description?.let { description?.let {
Text( Text(
@@ -4,16 +4,7 @@ import android.os.Build
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.*
import androidx.compose.material.icons.rounded.CopyAll
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Download
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material.icons.rounded.PlayArrow
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material.icons.rounded.SelectAll
import androidx.compose.material.icons.rounded.Stop
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -197,6 +188,14 @@ fun currentNavBackStackEntryAsNavBarState(
route = Route.Display, route = Route.Display,
) )
backStackEntry.isCurrentRoute(Route.WifiDetectionMethod::class) ->
NavBarState(
showTop = true,
showBottom = true,
topTitle = { Text(stringResource(R.string.wifi_detection_method)) },
route = Route.WifiDetectionMethod,
)
backStackEntry.isCurrentRoute(Route.KillSwitch::class) -> backStackEntry.isCurrentRoute(Route.KillSwitch::class) ->
NavBarState( NavBarState(
showTop = true, showTop = true,
@@ -240,8 +239,17 @@ fun currentNavBackStackEntryAsNavBarState(
showBottom = true, showBottom = true,
topTitle = { tunnel?.name?.let { Text(it) } }, topTitle = { tunnel?.name?.let { Text(it) } },
topTrailing = { topTrailing = {
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) { Row {
tunnel?.id?.let { navController.navigate(Route.Config(it)) } ActionIconButton(Icons.Rounded.QrCode2, R.string.show_qr) {
tunnel?.id?.let {
viewModel.handleEvent(
AppEvent.SetShowModal(AppViewState.ModalType.QR)
)
}
}
ActionIconButton(Icons.Rounded.Edit, R.string.edit_tunnel) {
tunnel?.id?.let { navController.navigate(Route.Config(it)) }
}
} }
}, },
route = args?.let { Route.TunnelOptions(it.id) }, route = args?.let { Route.TunnelOptions(it.id) },
@@ -1,18 +1,9 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.*
import androidx.compose.material.icons.outlined.Filter1
import androidx.compose.material.icons.outlined.Security
import androidx.compose.material.icons.outlined.VpnKeyOff
import androidx.compose.material.icons.outlined.Wifi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -26,14 +17,19 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
import com.zaneschepke.networkmonitor.NetworkStatus import com.zaneschepke.networkmonitor.NetworkStatus
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberClipboardHelper
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
@@ -47,6 +43,7 @@ fun WifiTunnelingItems(
isWifiNameReadable: () -> Boolean, isWifiNameReadable: () -> Boolean,
): List<SelectionItem> { ): List<SelectionItem> {
val context = LocalContext.current val context = LocalContext.current
val navController = LocalNavController.current
val clipboardHelper = rememberClipboardHelper() val clipboardHelper = rememberClipboardHelper()
val baseItems = val baseItems =
@@ -107,40 +104,40 @@ fun WifiTunnelingItems(
} }
}, },
onClick = { viewModel.handleEvent(AppEvent.ToggleAutoTunnelOnWifi) }, onClick = { viewModel.handleEvent(AppEvent.ToggleAutoTunnelOnWifi) },
), )
SelectionItem(
leadingIcon = Icons.Outlined.Code,
title = {
Text(
stringResource(R.string.wifi_name_via_shell),
style =
MaterialTheme.typography.bodyMedium.copy(
MaterialTheme.colorScheme.onSurface
),
)
},
description = {
Text(
stringResource(R.string.use_root_shell_for_wifi),
style =
MaterialTheme.typography.bodySmall.copy(
MaterialTheme.colorScheme.outline
),
)
},
trailing = {
ScaledSwitch(
checked = uiState.appSettings.isWifiNameByShellEnabled,
onClick = { viewModel.handleEvent(AppEvent.ToggleRootShellWifi) },
)
},
onClick = { viewModel.handleEvent(AppEvent.ToggleRootShellWifi) },
),
) )
return if (uiState.appSettings.isTunnelOnWifiEnabled) { return if (uiState.appSettings.isTunnelOnWifiEnabled) {
baseItems + baseItems +
listOf( listOf(
SelectionItem(
leadingIcon = Icons.Outlined.WifiFind,
title = {
Text(
stringResource(R.string.wifi_detection_method),
style =
MaterialTheme.typography.bodyMedium.copy(
MaterialTheme.colorScheme.onSurface
),
)
},
description = {
Text(
stringResource(
R.string.current_template,
uiState.appSettings.wifiDetectionMethod.asString(context),
),
style =
MaterialTheme.typography.bodySmall.copy(
MaterialTheme.colorScheme.outline
),
)
},
trailing = {
ForwardButton { navController.navigate(Route.WifiDetectionMethod) }
},
onClick = { navController.navigate(Route.WifiDetectionMethod) },
),
SelectionItem( SelectionItem(
leadingIcon = Icons.Outlined.Filter1, leadingIcon = Icons.Outlined.Filter1,
title = { title = {
@@ -205,7 +202,10 @@ fun WifiTunnelingItems(
currentText = currentText, currentText = currentText,
onSave = { ssid -> onSave = { ssid ->
if ( if (
uiState.appSettings.isWifiNameByShellEnabled || uiState.appSettings.wifiDetectionMethod ==
AndroidNetworkMonitor.WifiDetectionMethod.ROOT ||
uiState.appSettings.wifiDetectionMethod ==
AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU ||
isWifiNameReadable() isWifiNameReadable()
) { ) {
viewModel.handleEvent(AppEvent.SaveTrustedSSID(ssid)) viewModel.handleEvent(AppEvent.SaveTrustedSSID(ssid))
@@ -0,0 +1,39 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.detection
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
import com.zaneschepke.wireguardautotunnel.ui.common.button.IconSurfaceButton
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
import com.zaneschepke.wireguardautotunnel.util.extensions.asDescriptionString
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
@Composable
fun WifiDetectionMethodScreen(uiState: AppUiState, viewModel: AppViewModel) {
val context = LocalContext.current
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
modifier = Modifier.fillMaxSize().padding(top = 24.dp).padding(horizontal = 24.dp),
) {
enumValues<AndroidNetworkMonitor.WifiDetectionMethod>().forEach {
val title = it.asString(context)
val description = it.asDescriptionString(context)
IconSurfaceButton(
title = title,
onClick = { viewModel.handleEvent(AppEvent.SetDetectionMethod(it)) },
selected = uiState.appSettings.wifiDetectionMethod == it,
description = description,
)
}
}
}
@@ -9,6 +9,8 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.Route import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
@@ -45,6 +47,15 @@ fun MainScreen(appUiState: AppUiState, appViewState: AppViewState, viewModel: Ap
onData = { data -> viewModel.handleEvent(AppEvent.ImportTunnelFromFile(data)) }, onData = { data -> viewModel.handleEvent(AppEvent.ImportTunnelFromFile(data)) },
) )
val scanLauncher =
rememberLauncherForActivityResult(
contract = ScanContract(),
onResult = { result ->
if (result != null && result.contents.isNotEmpty())
viewModel.handleEvent(AppEvent.ImportTunnelFromQrCode(result.contents))
},
)
val requestPermissionLauncher = val requestPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted
-> ->
@@ -56,7 +67,9 @@ fun MainScreen(appUiState: AppUiState, appViewState: AppViewState, viewModel: Ap
) )
return@rememberLauncherForActivityResult return@rememberLauncherForActivityResult
} }
navController.navigate(Route.Scanner) scanLauncher.launch(
ScanOptions().setDesiredBarcodeFormats(ScanOptions.QR_CODE).setBeepEnabled(false)
)
} }
if (appViewState.showModal == AppViewState.ModalType.DELETE) { if (appViewState.showModal == AppViewState.ModalType.DELETE) {
@@ -15,8 +15,8 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.components.EthernetTunnelItem import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.components.EthernetTunnelItem
import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.components.MobileDataTunnelItem import com.zaneschepke.wireguardautotunnel.ui.screens.main.autotunnel.components.MobileDataTunnelItem
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
@@ -16,8 +16,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.TrustedNetworkTextBox import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.TrustedNetworkTextBox
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.WildcardsLabel
@@ -93,7 +93,7 @@ fun ExportTunnelsBottomSheet(viewModel: AppViewModel) {
ExportOptionRow( ExportOptionRow(
label = stringResource(R.string.export_tunnels_amnezia), label = stringResource(R.string.export_tunnels_amnezia),
onClick = { onClick = {
exportConfigType = ConfigType.AMNEZIA exportConfigType = ConfigType.AM
if (!isAuthorized && !isTv) { if (!isAuthorized && !isTv) {
showAuthPrompt = true showAuthPrompt = true
} else { } else {
@@ -16,7 +16,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.core.tunnel.getValueById import com.zaneschepke.wireguardautotunnel.core.tunnel.getValueById
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.Route import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalIsAndroidTV import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalIsAndroidTV
@@ -22,7 +22,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
@@ -16,7 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.util.NumberUtils import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import com.zaneschepke.wireguardautotunnel.util.extensions.toThreeDecimalPlaceString import com.zaneschepke.wireguardautotunnel.util.extensions.toThreeDecimalPlaceString
@@ -20,7 +20,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.MainActivity import com.zaneschepke.wireguardautotunnel.MainActivity
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.AddPeerButton import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.AddPeerButton
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.InterfaceSection import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.InterfaceSection
@@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
@@ -1,33 +0,0 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.main.scanner
import android.app.Activity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.journeyapps.barcodescanner.CompoundBarcodeView
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
@Composable
fun ScannerScreen(viewModel: AppViewModel) {
val context = LocalContext.current
val barcodeView = remember {
CompoundBarcodeView(context).apply {
this.initializeFromIntent((context as Activity).intent)
this.setStatusText("")
this.decodeSingle { result ->
result.text?.let { barCodeOrQr ->
viewModel.handleEvent(AppEvent.ImportTunnelFromQrCode(barCodeOrQr))
}
}
}
}
AndroidView(factory = { barcodeView })
DisposableEffect(Unit) {
barcodeView.resume()
onDispose { barcodeView.pause() }
}
}
@@ -4,7 +4,7 @@ import android.content.Context
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import com.zaneschepke.wireguardautotunnel.ui.Route import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.state.SplitOption import com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.state.SplitOption
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.state package com.zaneschepke.wireguardautotunnel.ui.screens.main.splittunnel.state
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
data class SplitTunnelUiState( data class SplitTunnelUiState(
val loading: Boolean = true, val loading: Boolean = true,
@@ -6,18 +6,53 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider import com.zaneschepke.wireguardautotunnel.ui.common.SectionDivider
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalIsAndroidTV
import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.components.* import com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.components.*
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.AuthorizationPromptWrapper
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
@Composable @Composable
fun TunnelOptionsScreen(tunnelConf: TunnelConf, viewModel: AppViewModel) { fun TunnelOptionsScreen(
tunnelConf: TunnelConf,
viewModel: AppViewModel,
appViewState: AppViewState,
) {
val isTv = LocalIsAndroidTV.current
var showAuthPrompt by remember { mutableStateOf(!isTv) }
var isAuthorized by remember { mutableStateOf(isTv) }
if (appViewState.showModal == AppViewState.ModalType.QR) {
// Show authorization prompt if needed
if (showAuthPrompt) {
AuthorizationPromptWrapper(
onDismiss = { showAuthPrompt = false },
onSuccess = {
showAuthPrompt = false
isAuthorized = true
},
viewModel = viewModel,
)
}
if (isAuthorized) {
QrCodeDialog(
tunnelConf = tunnelConf,
onDismiss = {
viewModel.handleEvent(AppEvent.SetShowModal(AppViewState.ModalType.NONE))
},
)
}
}
Column( Column(
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.Route import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
@@ -9,7 +9,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
@@ -0,0 +1,190 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.main.tunneloptions.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material.icons.outlined.VpnKey
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import com.zaneschepke.wireguardautotunnel.MainActivity
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.util.extensions.setScreenBrightness
import io.github.alexzhirkevich.qrose.options.QrBallShape
import io.github.alexzhirkevich.qrose.options.QrBrush
import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel
import io.github.alexzhirkevich.qrose.options.QrFrameShape
import io.github.alexzhirkevich.qrose.options.QrOptions
import io.github.alexzhirkevich.qrose.options.QrPixelShape
import io.github.alexzhirkevich.qrose.options.circle
import io.github.alexzhirkevich.qrose.options.roundCorners
import io.github.alexzhirkevich.qrose.options.solid
import io.github.alexzhirkevich.qrose.rememberQrCodePainter
@Composable
fun QrCodeDialog(tunnelConf: TunnelConf, onDismiss: () -> Unit) {
val context = LocalContext.current
val activity = context as? MainActivity
// Handle screen brightness
DisposableEffect(Unit) {
activity?.setScreenBrightness(1.0f)
onDispose { activity?.setScreenBrightness(-1f) }
}
QrCodeAlertDialog(tunnelConf = tunnelConf, onDismiss = onDismiss)
}
@Composable
private fun QrCodeAlertDialog(tunnelConf: TunnelConf, onDismiss: () -> Unit) {
Surface(color = Color.White, tonalElevation = 0.dp) {
AlertDialog(
containerColor = Color.White,
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.done), color = MaterialTheme.colorScheme.surface)
}
},
title = {
Text(
text = tunnelConf.name,
color = Color.Black,
style = MaterialTheme.typography.titleLarge,
)
},
text = { QrCodeContent(tunnelConf = tunnelConf) },
properties = DialogProperties(usePlatformDefaultWidth = true),
)
}
}
@Composable
private fun QrCodeContent(tunnelConf: TunnelConf) {
var selectedOption by remember { mutableStateOf(ConfigType.WG) }
val qrCodeText =
when (selectedOption) {
ConfigType.AM -> tunnelConf.toAmConfig().toAwgQuickString(true)
ConfigType.WG -> tunnelConf.toWgConfig().toWgQuickString(true)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
) {
val qrCodePainter = rememberQrCodePainter(data = qrCodeText, options = createQrOptions())
Image(
painter = qrCodePainter,
contentDescription = stringResource(R.string.show_qr),
modifier =
Modifier.size(300.dp)
.align(Alignment.CenterHorizontally)
.padding(16.dp)
.background(Color.White),
)
ConfigTypeSelector(
selectedOption = selectedOption,
onOptionSelected = { selectedOption = it },
)
}
}
@Composable
private fun ConfigTypeSelector(selectedOption: ConfigType, onOptionSelected: (ConfigType) -> Unit) {
MultiChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)) {
ConfigType.entries.sortedDescending().forEachIndexed { index, entry ->
val isActive = selectedOption == entry
val typeName =
stringResource(
when (entry) {
ConfigType.AM -> R.string.amnezia
ConfigType.WG -> R.string.wireguard
}
)
SegmentedButton(
shape =
SegmentedButtonDefaults.itemShape(
index = index,
count = ConfigType.entries.size,
baseShape = RoundedCornerShape(8.dp),
),
icon = {
SegmentedButtonDefaults.Icon(
active = isActive,
activeContent = {
Icon(
imageVector = Icons.Outlined.Check,
contentDescription = stringResource(R.string.select),
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
)
},
) {
Icon(
imageVector = Icons.Outlined.VpnKey,
contentDescription = typeName,
tint = Color.Black,
modifier = Modifier.size(SegmentedButtonDefaults.IconSize),
)
}
},
colors =
SegmentedButtonDefaults.colors()
.copy(
activeContainerColor = Color.White,
inactiveContainerColor = Color.White,
),
onCheckedChange = { onOptionSelected(entry) },
checked = isActive,
) {
Text(
text = typeName,
color = Color.Black,
style = MaterialTheme.typography.labelMedium,
)
}
}
}
}
private fun createQrOptions(): QrOptions = QrOptions {
shapes {
darkPixel = QrPixelShape.circle()
ball = QrBallShape.circle()
frame = QrFrameShape.roundCorners(0.2f)
}
colors {
dark = QrBrush.solid(Color.Black)
frame = QrBrush.solid(Color.Black)
ball = QrBrush.solid(Color.Black)
}
errorCorrectionLevel = QrErrorCorrectionLevel.Medium
}
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
@@ -7,7 +7,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.Route import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton import com.zaneschepke.wireguardautotunnel.ui.common.button.ForwardButton
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
@@ -12,6 +12,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withLink
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -72,8 +78,41 @@ fun SupportScreen(viewModel: SupportViewModel = hiltViewModel(), appViewModel: A
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top), verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
Text(uiState.appUpdate?.version ?: "") val annotatedString = buildAnnotatedString {
Text(uiState.appUpdate?.releaseNotes ?: "") append("${uiState.appUpdate?.version ?: ""}\n")
// Add clickable text for second line
withLink(
link =
LinkAnnotation.Clickable(
tag = stringResource(id = R.string.release_notes),
linkInteractionListener = {
val version =
if (BuildConfig.VERSION_NAME.contains("nightly")) {
"nightly"
} else {
uiState.appUpdate
?.version
?.removePrefix("v")
?.trim() ?: ""
}
val url = "${Constants.BASE_RELEASE_URL}$version".trim()
context.openWebUrl(url)
},
styles =
TextLinkStyles(
style =
SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline,
)
),
)
) {
append(stringResource(R.string.release_notes))
}
}
Text(text = annotatedString)
if (uiState.isLoading) { if (uiState.isLoading) {
LinearProgressIndicator( LinearProgressIndicator(
progress = { uiState.downloadProgress }, progress = { uiState.downloadProgress },
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support package com.zaneschepke.wireguardautotunnel.ui.screens.support
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.StringValue
data class SupportUiState( data class SupportUiState(
@@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.BuildConfig import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.entity.AppUpdate import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository
import com.zaneschepke.wireguardautotunnel.util.FileUtils import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.StringValue
@@ -1,17 +1,18 @@
package com.zaneschepke.wireguardautotunnel.ui.state package com.zaneschepke.wireguardautotunnel.ui.state
import com.zaneschepke.networkmonitor.NetworkStatus import com.zaneschepke.networkmonitor.NetworkStatus
import com.zaneschepke.wireguardautotunnel.data.model.GeneralState import com.zaneschepke.wireguardautotunnel.data.entity.GeneralState
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings import com.zaneschepke.wireguardautotunnel.data.mapper.GeneralStateMapper
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.AppState
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
data class AppUiState( data class AppUiState(
val appSettings: AppSettings = AppSettings(), val appSettings: AppSettings = AppSettings(),
val tunnels: List<TunnelConf> = emptyList(), val tunnels: List<TunnelConf> = emptyList(),
val activeTunnels: Map<TunnelConf, TunnelState> = emptyMap(), val activeTunnels: Map<TunnelConf, TunnelState> = emptyMap(),
val appState: AppState = GeneralState().toAppState(), val appState: AppState = GeneralStateMapper.toAppState(GeneralState()),
val isAutoTunnelActive: Boolean = false, val isAutoTunnelActive: Boolean = false,
val appConfigurationChange: Boolean = false, val appConfigurationChange: Boolean = false,
val isAppLoaded: Boolean = false, val isAppLoaded: Boolean = false,
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.state package com.zaneschepke.wireguardautotunnel.ui.state
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.StringValue
data class AppViewState( data class AppViewState(
@@ -18,6 +18,7 @@ data class AppViewState(
NONE, NONE,
DELETE, DELETE,
INFO, INFO,
QR,
} }
enum class BottomSheet { enum class BottomSheet {
@@ -1,7 +1,7 @@
package com.zaneschepke.wireguardautotunnel.ui.state package com.zaneschepke.wireguardautotunnel.ui.state
import com.wireguard.config.Peer import com.wireguard.config.Peer
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.util.extensions.joinAndTrim import com.zaneschepke.wireguardautotunnel.util.extensions.joinAndTrim
data class PeerProxy( data class PeerProxy(
@@ -38,4 +38,6 @@ object Constants {
const val GOOGLE_PLAY_FLAVOR = "google" const val GOOGLE_PLAY_FLAVOR = "google"
const val STANDALONE_FLAVOR = "standalone" const val STANDALONE_FLAVOR = "standalone"
const val RELEASE = "release" const val RELEASE = "release"
const val BASE_RELEASE_URL = "https://github.com/wgtunnel/wgtunnel/releases/tag/"
} }
@@ -10,8 +10,8 @@ import android.provider.OpenableColumns
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.util.extensions.getInputStreamFromUri import com.zaneschepke.wireguardautotunnel.util.extensions.getInputStreamFromUri
import com.zaneschepke.wireguardautotunnel.util.extensions.installApk import com.zaneschepke.wireguardautotunnel.util.extensions.installApk
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
@@ -1,6 +1,7 @@
package com.zaneschepke.wireguardautotunnel.util.extensions package com.zaneschepke.wireguardautotunnel.util.extensions
import android.Manifest import android.Manifest
import android.app.Activity
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Context.POWER_SERVICE import android.content.Context.POWER_SERVICE
@@ -17,6 +18,9 @@ import android.widget.Toast
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.location.LocationManagerCompat import androidx.core.location.LocationManagerCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
@@ -225,3 +229,22 @@ fun Context.installApk(apkFile: File) {
} }
startActivity(intent) startActivity(intent)
} }
fun Activity.setScreenBrightness(brightness: Float) {
window.attributes = window.attributes.apply { screenBrightness = brightness }
}
fun Activity.enableImmersiveMode() {
WindowCompat.setDecorFitsSystemWindows(window, false)
val controller = WindowCompat.getInsetsController(window, window.decorView)
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
controller.hide(WindowInsetsCompat.Type.systemBars())
}
fun Activity.disableImmersiveMode() {
WindowCompat.setDecorFitsSystemWindows(window, true)
val controller = WindowCompat.getInsetsController(window, window.decorView)
controller.show(WindowInsetsCompat.Type.systemBars())
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
@@ -1,8 +1,8 @@
package com.zaneschepke.wireguardautotunnel.util.extensions package com.zaneschepke.wireguardautotunnel.util.extensions
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import java.math.BigDecimal import java.math.BigDecimal
import java.text.DecimalFormat import java.text.DecimalFormat
@@ -1,6 +1,9 @@
package com.zaneschepke.wireguardautotunnel.util.extensions package com.zaneschepke.wireguardautotunnel.util.extensions
import android.content.Context
import androidx.navigation.NavController import androidx.navigation.NavController
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.Route import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.navigation.isCurrentRoute import com.zaneschepke.wireguardautotunnel.ui.navigation.isCurrentRoute
@@ -11,3 +14,25 @@ fun NavController.goFromRoot(route: Route) {
launchSingleTop = true launchSingleTop = true
} }
} }
fun AndroidNetworkMonitor.WifiDetectionMethod.asString(context: Context): String {
return when (this) {
AndroidNetworkMonitor.WifiDetectionMethod.DEFAULT -> context.getString(R.string._default)
AndroidNetworkMonitor.WifiDetectionMethod.LEGACY -> context.getString(R.string.legacy)
AndroidNetworkMonitor.WifiDetectionMethod.ROOT -> context.getString(R.string.root)
AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU -> context.getString(R.string.shizuku)
}
}
fun AndroidNetworkMonitor.WifiDetectionMethod.asDescriptionString(context: Context): String? {
return when (this) {
AndroidNetworkMonitor.WifiDetectionMethod.LEGACY ->
context.getString(R.string.legacy_api_description)
AndroidNetworkMonitor.WifiDetectionMethod.ROOT ->
context.getString(R.string.use_root_shell_for_wifi)
AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU ->
context.getString(R.string.use_shell_via_shizuku)
AndroidNetworkMonitor.WifiDetectionMethod.DEFAULT ->
context.getString(R.string.use_android_recommended)
}
}
@@ -1,5 +1,6 @@
package com.zaneschepke.wireguardautotunnel.viewmodel package com.zaneschepke.wireguardautotunnel.viewmodel
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -8,6 +9,7 @@ import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.util.RootShell import com.wireguard.android.util.RootShell
import com.zaneschepke.logcatter.LogReader import com.zaneschepke.logcatter.LogReader
import com.zaneschepke.logcatter.model.LogMessage import com.zaneschepke.logcatter.model.LogMessage
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
import com.zaneschepke.networkmonitor.NetworkMonitor import com.zaneschepke.networkmonitor.NetworkMonitor
import com.zaneschepke.networkmonitor.NetworkStatus import com.zaneschepke.networkmonitor.NetworkStatus
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
@@ -18,11 +20,11 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
import com.zaneschepke.wireguardautotunnel.di.AppShell import com.zaneschepke.wireguardautotunnel.di.AppShell
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.entity.AppState
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
import com.zaneschepke.wireguardautotunnel.domain.model.AppState
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
@@ -39,14 +41,13 @@ import java.time.Instant
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
import kotlin.collections.component1
import kotlin.collections.component2
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import org.amnezia.awg.config.BadConfigException import org.amnezia.awg.config.BadConfigException
import org.amnezia.awg.config.Config import org.amnezia.awg.config.Config
import rikka.shizuku.Shizuku
import timber.log.Timber import timber.log.Timber
import xyz.teamgravity.pin_lock_compose.PinManager import xyz.teamgravity.pin_lock_compose.PinManager
@@ -179,7 +180,6 @@ constructor(
handleDeleteTrustedSSID(event.ssid, state.appSettings) handleDeleteTrustedSSID(event.ssid, state.appSettings)
AppEvent.ToggleAutoTunnelWildcards -> AppEvent.ToggleAutoTunnelWildcards ->
handleToggleAutoTunnelWildcards(state.appSettings) handleToggleAutoTunnelWildcards(state.appSettings)
AppEvent.ToggleRootShellWifi -> handleToggleRootShellWifi(state.appSettings)
is AppEvent.SaveTrustedSSID -> is AppEvent.SaveTrustedSSID ->
handleSaveTrustedSSID(event.ssid, state.appSettings) handleSaveTrustedSSID(event.ssid, state.appSettings)
AppEvent.ToggleAutoTunnelOnEthernet -> AppEvent.ToggleAutoTunnelOnEthernet ->
@@ -212,10 +212,46 @@ constructor(
AppEvent.ClearSelectedTunnels -> clearSelectedTunnels() AppEvent.ClearSelectedTunnels -> clearSelectedTunnels()
is AppEvent.SetShowModal -> is AppEvent.SetShowModal ->
_appViewState.update { it.copy(showModal = event.modalType) } _appViewState.update { it.copy(showModal = event.modalType) }
is AppEvent.SetDetectionMethod ->
handleSetDetectionMethod(event.detectionMethod, state.appSettings)
} }
} }
} }
private suspend fun handleSetDetectionMethod(
detectionMethod: AndroidNetworkMonitor.WifiDetectionMethod,
appSettings: AppSettings,
) {
if (detectionMethod == appSettings.wifiDetectionMethod) return
when (detectionMethod) {
AndroidNetworkMonitor.WifiDetectionMethod.ROOT -> if (!requestRoot()) return
AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU -> {
Shizuku.addRequestPermissionResultListener(
Shizuku.OnRequestPermissionResultListener { requestCode: Int, grantResult: Int
->
if (grantResult != PERMISSION_GRANTED)
return@OnRequestPermissionResultListener
viewModelScope.launch {
saveSettings(appSettings.copy(wifiDetectionMethod = detectionMethod))
}
}
)
try {
if (Shizuku.checkSelfPermission() != PERMISSION_GRANTED)
return Shizuku.requestPermission(123)
} catch (e: Exception) {
Timber.e(e)
return handleShowMessage(
StringValue.StringResource(R.string.shizuku_not_detected)
)
}
}
else -> Unit
}
saveSettings(appSettings.copy(wifiDetectionMethod = detectionMethod))
}
private fun handleToggleSelectAllTunnels(tunnels: List<TunnelConf>) = private fun handleToggleSelectAllTunnels(tunnels: List<TunnelConf>) =
_appViewState.update { it -> _appViewState.update { it ->
val remove = tunnels.size == it.selectedTunnels.size val remove = tunnels.size == it.selectedTunnels.size
@@ -266,6 +302,7 @@ constructor(
} }
} }
// TODO
private fun handleTunnelErrors() = private fun handleTunnelErrors() =
viewModelScope.launch { tunnelManager.errorEvents.collect { errorEvent -> } } viewModelScope.launch { tunnelManager.errorEvents.collect { errorEvent -> } }
@@ -632,14 +669,6 @@ constructor(
) )
) )
private suspend fun handleToggleRootShellWifi(appSettings: AppSettings) {
if (requestRoot()) {
saveSettings(
appSettings.copy(isWifiNameByShellEnabled = !appSettings.isWifiNameByShellEnabled)
)
}
}
private suspend fun handleToggleTunnelOnEthernet(appSettings: AppSettings) = private suspend fun handleToggleTunnelOnEthernet(appSettings: AppSettings) =
saveSettings( saveSettings(
appSettings.copy(isTunnelOnEthernetEnabled = !appSettings.isTunnelOnEthernetEnabled) appSettings.copy(isTunnelOnEthernetEnabled = !appSettings.isTunnelOnEthernetEnabled)
@@ -690,7 +719,7 @@ constructor(
if (tunnels.isEmpty()) return if (tunnels.isEmpty()) return
val (files, shareFileName) = val (files, shareFileName) =
when (configType) { when (configType) {
ConfigType.AMNEZIA -> { ConfigType.AM -> {
val amFiles = fileUtils.createAmFiles(tunnels) val amFiles = fileUtils.createAmFiles(tunnels)
if (amFiles.isEmpty()) { if (amFiles.isEmpty()) {
throw IOException("No valid Amnezia config files created") throw IOException("No valid Amnezia config files created")
@@ -1,8 +1,9 @@
package com.zaneschepke.wireguardautotunnel.viewmodel.event package com.zaneschepke.wireguardautotunnel.viewmodel.event
import android.net.Uri import android.net.Uri
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
import com.zaneschepke.wireguardautotunnel.ui.theme.Theme import com.zaneschepke.wireguardautotunnel.ui.theme.Theme
import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.StringValue
@@ -78,6 +79,9 @@ sealed class AppEvent {
data class SetTheme(val theme: Theme) : AppEvent() data class SetTheme(val theme: Theme) : AppEvent()
data class SetDetectionMethod(val detectionMethod: AndroidNetworkMonitor.WifiDetectionMethod) :
AppEvent()
data object ToggleAutoTunnelOnWifi : AppEvent() data object ToggleAutoTunnelOnWifi : AppEvent()
data object ToggleAutoTunnelOnCellular : AppEvent() data object ToggleAutoTunnelOnCellular : AppEvent()
@@ -90,8 +94,6 @@ sealed class AppEvent {
data object ToggleAutoTunnelWildcards : AppEvent() data object ToggleAutoTunnelWildcards : AppEvent()
data object ToggleRootShellWifi : AppEvent()
data class DeleteTrustedSSID(val ssid: String) : AppEvent() data class DeleteTrustedSSID(val ssid: String) : AppEvent()
data class SaveTrustedSSID(val ssid: String) : AppEvent() data class SaveTrustedSSID(val ssid: String) : AppEvent()
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
+159 -1
View File
@@ -82,7 +82,7 @@
<string name="no_browser_detected">Žádný prohlížeč nebyl nalezen</string> <string name="no_browser_detected">Žádný prohlížeč nebyl nalezen</string>
<string name="read_logs">Přečíst si logy</string> <string name="read_logs">Přečíst si logy</string>
<string name="pin_created">PIN úspěšně vytvořen</string> <string name="pin_created">PIN úspěšně vytvořen</string>
<string name="enter_pin">Vložte Váš PIN</string> <string name="enter_pin">Zadejte Váš PIN</string>
<string name="enable_app_lock">Zapnout zámek aplikace</string> <string name="enable_app_lock">Zapnout zámek aplikace</string>
<string name="restart_on_ping">Restartovat při selhání pingu</string> <string name="restart_on_ping">Restartovat při selhání pingu</string>
<string name="mobile_data_tunnel">Nastavit jako tunel pro mobilní data</string> <string name="mobile_data_tunnel">Nastavit jako tunel pro mobilní data</string>
@@ -163,4 +163,162 @@
<string name="learn_more">Zjistit více</string> <string name="learn_more">Zjistit více</string>
<string name="stop">zastavit</string> <string name="stop">zastavit</string>
<string name="server_ipv4">Překlad názvu hostitele IPv4</string> <string name="server_ipv4">Překlad názvu hostitele IPv4</string>
<string name="select_all">Vybrat vše</string>
<string name="share">Sdílet</string>
<string name="trusted_ssid_value_description">Odeslat SSID</string>
<string name="app_settings">nastavení aplikace</string>
<string name="debounce_delay">Zpoždění odezvy</string>
<string name="always_on_message">Autorizace připojení VPN byla zamítnuta. Zkontrolujte prosím</string>
<string name="bio_not_created">Biometrické údaje nebyly vytvořeny</string>
<string name="bio_not_supported">Biometrie není podporována</string>
<string name="bio_subtitle">Přihlášení pomocí biometrických údajů</string>
<string name="config_error">Chybná konfigurace</string>
<string name="prominent_background_location_title">Zpřístupnění stávající polohy na pozadí</string>
<string name="vpn_denied_dialog_title">Povolení zamítnuto</string>
<string name="app_permission_title">Řídicí most pro WG tunely</string>
<string name="app_permission_description">Ovládání funkcí tunelu a automatického tunelu.</string>
<string name="enable_remote_app_control">Povolit vzdálené ovládání aplikace</string>
<string name="tunnel_starting">Spuštění tunelu</string>
<string name="bio_auth_title">Biometrické ověření</string>
<string name="nothing_here_yet">Zatím zde nic není!</string>
<string name="export_success">Export byl úspěšně dokončen</string>
<string name="download">Stáhnout</string>
<string name="check_for_update">Zkontrolovat aktualizaci</string>
<string name="update_check_failed">Kontrola aktualizace se nezdařila.</string>
<string name="version_template">Verze: %1$s</string>
<string name="update_download_failed">Stažení aktualizace se nezdařilo.</string>
<string name="update_available">Dostupná aktualizace!</string>
<string name="download_and_install">Stáhnout a nainstalovat</string>
<string name="allow">Povolit</string>
<string name="permission_required">Je vyžadováno oprávnění</string>
<string name="licenses">Licence</string>
<string name="latest_installed">Již používáte nejnovější verzi.</string>
<string name="install_updated_permission">Tato aplikace potřebuje oprávnění k instalaci aktualizací.</string>
<string name="checking_for_update">Kontrola aktualizací</string>
<string name="add_from_url">Přidat z adresy URL</string>
<string name="inactive">Neaktivní</string>
<string name="auth_error">Chyba: neautorizováno</string>
<string name="kernel_name_error">Chyba názvu modulu jádra</string>
<string name="export_failed">Export se nezdařil</string>
<string name="delete">Smazat</string>
<string name="export_tunnels_wireguard">Exportovat tunely jako WireGuard</string>
<string name="export_tunnels_amnezia">Exportovat tunely jako Amnezia</string>
<string name="remote_key_template">Klíč: %1$s</string>
<string name="active">Aktivní</string>
<string name="service_running_error">Chyba: Služba není spuštěna</string>
<string name="wifi_name_template">Aktivní: %1$s</string>
<string name="tunnel_error_template">Tunel selhal s: %1$s</string>
<string name="camera_permission_required">Vyžadováno oprávnění k použití fotoaparátu</string>
<string name="info">Informace</string>
<string name="copy">Kopírovat</string>
<string name="status">Stav</string>
<string name="launch_app_settings">Spustit nastavení aplikace</string>
<string name="tunnel_running">Tunel je v provozu</string>
<string name="wildcards_active">Zástupné znaky(wildcards) aktivní</string>
<string name="root_accepted">Root shell přijata</string>
<string name="background_location_message">Autorizace povolit vždy polohu a/nebo přesná poloha je vyžadováno pro tuto funkci. Viz</string>
<string name="update_check_unsupported">Kontrola aktualizací není u tohoto typu sestavení podporována.</string>
<string name="background_location_message2">abyste se ujistili, že jsou tato oprávnění povolena</string>
<string name="darker">Tmavší</string>
<string name="amoled">AMOLED</string>
<string name="default_ping_ip">(nepovinné, výchozí hodnota je peers)</string>
<string name="monitoring_state_changes">Monitorování změn stavu</string>
<string name="pre_up">Před aktivací</string>
<string name="pre_down">Před deaktivací</string>
<string name="post_up">Po aktivaci</string>
<string name="optional_default">"nepovinné, výchozí: "</string>
<string name="flavor_template">Varianta: %1$s</string>
<string name="security_template">Zabezpečení: %1$s</string>
<string name="done">Hotovo</string>
<string name="wireguard">WireGuard</string>
<string name="amnezia">Amnezia</string>
<string name="show_qr">Zobrazit QR kód</string>
<string name="always_on_message2">ujistěte se, že je pro všechny ostatní aplikace vypnutá funkce trvalé připojení VPN, a zkuste to znovu</string>
<string name="use_wildcards">Použít zástupné znaky(wildcards) pro názvy</string>
<string name="multiple">Několik</string>
<string name="enter_config_url">Zadejte adresu URL konfigurace</string>
<string name="search">Hledat</string>
<string name="join_matrix">Připojte se k Matrix komunitě</string>
<string name="join_telegram">Připojte se k Telegram komunitě</string>
<string name="post_down">Po deaktivaci</string>
<string name="invalid_config_error">chyba_neplatné_konfigurace</string>
<string name="error_download_failed">Nepodařilo se stáhnout konfiguraci</string>
<string name="select">Vybrat</string>
<string name="save">Uložit</string>
<string name="dns_resolve_error">Chyba překladu dns</string>
<string name="bio_update_required">Vyžadována aktualizace biometrického zabezpečení</string>
<string name="export_logs">Exportovat uložené protokoly</string>
<string name="add_tunnel">Přidat tunel</string>
<string name="delete_logs">Smazat a vyčistit protokoly</string>
<string name="dropdown">Rozbalovací nabídka</string>
<string name="select_all">Vybrat vše</string>
<string name="share">Sdílet</string>
<string name="trusted_ssid_value_description">Odeslat SSID</string>
<string name="app_settings">nastavení aplikace</string>
<string name="debounce_delay">Debounce zpoždění</string>
<string name="always_on_message">Autorizace připojení VPN byla zamítnuta. Zkontrolujte prosím</string>
<string name="bio_not_created">Biometrické údaje nebyly vytvořeny</string>
<string name="bio_not_supported">Biometrie není podporována</string>
<string name="bio_subtitle">Přihlášení pomocí biometrických údajů</string>
<string name="config_error">Chybná konfigurace</string>
<string name="prominent_background_location_title">Zpřístupnění stávající polohy na pozadí</string>
<string name="vpn_denied_dialog_title">Povolení zamítnuto</string>
<string name="app_permission_title">Řídicí most pro WG tunely</string>
<string name="app_permission_description">Ovládání funkcí tunelu a automatického tunelu.</string>
<string name="enable_remote_app_control">Povolit vzdálené ovládání aplikace</string>
<string name="tunnel_starting">Spuštění tunelu</string>
<string name="bio_auth_title">Biometrické ověření</string>
<string name="nothing_here_yet">Zatím zde nic není!</string>
<string name="export_success">Export byl úspěšně dokončen</string>
<string name="download">Stáhnout</string>
<string name="check_for_update">Zkontrolovat aktualizaci</string>
<string name="update_check_failed">Kontrola aktualizace se nezdařila.</string>
<string name="version_template">Verze: %1$s</string>
<string name="update_download_failed">Stažení aktualizace se nezdařilo.</string>
<string name="update_available">Dostupná aktualizace!</string>
<string name="download_and_install">Stáhnout a nainstalovat</string>
<string name="allow">Povolit</string>
<string name="permission_required">Je vyžadováno oprávnění</string>
<string name="licenses">Licence</string>
<string name="latest_installed">Již používáte nejnovější verzi.</string>
<string name="install_updated_permission">Tato aplikace potřebuje oprávnění k instalaci aktualizací.</string>
<string name="checking_for_update">Kontrola aktualizací</string>
<string name="add_from_url">Přidat z adresy URL</string>
<string name="inactive">Neaktivní</string>
<string name="auth_error">Chyba: neautorizováno</string>
<string name="kernel_name_error">Chyba názvu modulu jádra</string>
<string name="export_failed">Export se nezdařil</string>
<string name="delete">Smazat</string>
<string name="export_tunnels_wireguard">Exportovat tunely jako WireGuard</string>
<string name="export_tunnels_amnezia">Exportovat tunely jako Amnezia</string>
<string name="remote_key_template">Klíč: %1$s</string>
<string name="active">Aktivní</string>
<string name="service_running_error">Chyba: Služba není spuštěna</string>
<string name="wifi_name_template">Aktivní: %1$s</string>
<string name="tunnel_error_template">Tunel selhal s: %1$s</string>
<string name="camera_permission_required">Vyžadováno oprávnění k použití fotoaparátu</string>
<string name="info">Informace</string>
<string name="copy">Kopírovat</string>
<string name="status">Stav</string>
<string name="launch_app_settings">Spustit nastavení aplikace</string>
<string name="tunnel_running">Tunel je v provozu</string>
<string name="wildcards_active">Wildcards aktivní</string>
<string name="root_accepted">Root shell přijata</string>
<string name="background_location_message">Autorizace povolit vždy polohu a/nebo přesná poloha je vyžadováno pro tuto funkci. Viz</string>
<string name="update_check_unsupported">Kontrola aktualizací není u tohoto typu sestavení podporována.</string>
<string name="background_location_message2">abyste se ujistili, že jsou tato oprávnění povolena</string>
<string name="darker">Tmavší</string>
<string name="amoled">AMOLED</string>
<string name="default_ping_ip">(nepovinné, výchozí hodnota je peers)</string>
<string name="monitoring_state_changes">Monitorování změn stavu</string>
<string name="pre_up">Před aktivací</string>
<string name="pre_down">Před deaktivací</string>
<string name="post_up">Po aktivaci</string>
<string name="optional_default">"nepovinné, výchozí: "</string>
<string name="flavor_template">Varianta: %1$s</string>
<string name="security_template">Zabezpečení: %1$s</string>
<string name="done">Hotovo</string>
<string name="wireguard">WireGuard</string>
<string name="amnezia">Amnezia</string>
<string name="show_qr">Zobrazit QR kód</string>
</resources> </resources>
+27 -2
View File
@@ -12,7 +12,7 @@
<string name="turn_on_tunnel">Für diese Aktion muss ein aktiver Tunnel bestehen</string> <string name="turn_on_tunnel">Für diese Aktion muss ein aktiver Tunnel bestehen</string>
<string name="prominent_background_location_message">Diese Funktion erfordert die Erlaubnis zur Standortbestimmung im Hintergrund, um die Überwachung der WLAN SSID zu ermöglichen, auch wenn die Anwendung geschlossen ist. Weitere Einzelheiten in den Datenschutzbestimmungen, die auf dem Support-Bildschirm verlinkt sind.</string> <string name="prominent_background_location_message">Diese Funktion erfordert die Erlaubnis zur Standortbestimmung im Hintergrund, um die Überwachung der WLAN SSID zu ermöglichen, auch wenn die Anwendung geschlossen ist. Weitere Einzelheiten in den Datenschutzbestimmungen, die auf dem Support-Bildschirm verlinkt sind.</string>
<string name="prominent_background_location_title">Vereinbarung der Standortberechtigung im Hintergrund</string> <string name="prominent_background_location_title">Vereinbarung der Standortberechtigung im Hintergrund</string>
<string name="thank_you">Danke fürs Benutzen von WG Tunnel!</string> <string name="thank_you">Danke für die Nutzung von WG Tunnel!</string>
<string name="trusted_ssid_value_description">SSID übermitteln</string> <string name="trusted_ssid_value_description">SSID übermitteln</string>
<string name="add_tunnels_text">Von Datei oder ZIP hinzufügen</string> <string name="add_tunnels_text">Von Datei oder ZIP hinzufügen</string>
<string name="open_file">Datei öffnen</string> <string name="open_file">Datei öffnen</string>
@@ -140,7 +140,7 @@
<string name="local_logging">Lokales Logging</string> <string name="local_logging">Lokales Logging</string>
<string name="enable_local_logging">Lokales Logging aktivieren</string> <string name="enable_local_logging">Lokales Logging aktivieren</string>
<string name="add_from_clipboard">Aus Zwischenablage einfügen</string> <string name="add_from_clipboard">Aus Zwischenablage einfügen</string>
<string name="kill_switch">Kill Switch</string> <string name="kill_switch">Notschalter</string>
<string name="automatic">Automatisch</string> <string name="automatic">Automatisch</string>
<string name="language">Sprache</string> <string name="language">Sprache</string>
<string name="display_theme">Anzeigetheme</string> <string name="display_theme">Anzeigetheme</string>
@@ -227,4 +227,29 @@
<string name="wifi_name_template">Aktiv: %1$s</string> <string name="wifi_name_template">Aktiv: %1$s</string>
<string name="delete">Löschen</string> <string name="delete">Löschen</string>
<string name="nothing_here_yet">Noch nix hier!</string> <string name="nothing_here_yet">Noch nix hier!</string>
<string name="share">Teilen</string>
<string name="select_all">Alles auswählen</string>
<string name="version_template">Version: %1$s</string>
<string name="export_success">Export Erfolg</string>
<string name="download">Download</string>
<string name="check_for_update">Auf Update prüfen</string>
<string name="update_check_failed">Updateprüfung fehlgeschlagen.</string>
<string name="checking_for_update">Überpüfe auf Updates</string>
<string name="update_download_failed">Updatedownload fehlgeschlagen.</string>
<string name="update_available">Update verfügbar!</string>
<string name="download_and_install">Herunterladen und installieren</string>
<string name="permission_required">Berechtigung erforderlich</string>
<string name="licenses">Lizenzen</string>
<string name="allow">Erlauben</string>
<string name="install_updated_permission">Diese App benötigt die Berechtigung, um Updates zu installieren.</string>
<string name="latest_installed">Du verwendesz bereits die neueste Version.</string>
<string name="security_template">Sicherheit: %1$s</string>
<string name="amoled">AMOLED</string>
<string name="flavor_template">Variante: %1$s</string>
<string name="darker">Dunkler</string>
<string name="update_check_unsupported">Updateprüfung wird bei diesem Build-Typ nicht unterstützt.</string>
<string name="wireguard">WireGuard</string>
<string name="done">Erledigt</string>
<string name="show_qr">QR anzeigen</string>
<string name="amnezia">Amnezia</string>
</resources> </resources>
+71 -5
View File
@@ -42,7 +42,7 @@
<string name="name">Nombre</string> <string name="name">Nombre</string>
<string name="always_on_vpn_support">Permitir VPN siempre-activada</string> <string name="always_on_vpn_support">Permitir VPN siempre-activada</string>
<string name="location_services_not_detected">Servicios de Ubicación No Detectados</string> <string name="location_services_not_detected">Servicios de Ubicación No Detectados</string>
<string name="auto_tunneling">Túnel-automático</string> <string name="auto_tunneling">Túnel automático</string>
<string name="vpn_on">VPN on</string> <string name="vpn_on">VPN on</string>
<string name="vpn_off">VPN off</string> <string name="vpn_off">VPN off</string>
<string name="create_import">Crear desde cero</string> <string name="create_import">Crear desde cero</string>
@@ -68,7 +68,7 @@
<string name="error_ssid_exists">SSID existente</string> <string name="error_ssid_exists">SSID existente</string>
<string name="error_root_denied">Shell root denegado</string> <string name="error_root_denied">Shell root denegado</string>
<string name="error_no_file_explorer">Explorador de archivos no instalado</string> <string name="error_no_file_explorer">Explorador de archivos no instalado</string>
<string name="auto_tunnel_title">Servicio de túnel-automático</string> <string name="auto_tunnel_title">Servicio de túnel automático</string>
<string name="delete_tunnel">Eliminar túnel</string> <string name="delete_tunnel">Eliminar túnel</string>
<string name="delete_tunnel_message">¿Estás seguro de que quieres eliminar este túnel?</string> <string name="delete_tunnel_message">¿Estás seguro de que quieres eliminar este túnel?</string>
<string name="yes"></string> <string name="yes"></string>
@@ -103,7 +103,7 @@
<string name="always_on_message">Se ha denegado el permiso de conexión VPN. Por favor, compruebe el</string> <string name="always_on_message">Se ha denegado el permiso de conexión VPN. Por favor, compruebe el</string>
<string name="always_on_message2">para asegurarse de que el VPN Siempre encendido esté desactivada para todas las demás aplicaciones e inténtelo de nuevo</string> <string name="always_on_message2">para asegurarse de que el VPN Siempre encendido esté desactivada para todas las demás aplicaciones e inténtelo de nuevo</string>
<string name="response_packet_magic_header">Encabezado del paquete de respuesta</string> <string name="response_packet_magic_header">Encabezado del paquete de respuesta</string>
<string name="junk_packet_maximum_size">Tamaño máximo del paquete basura</string> <string name="junk_packet_maximum_size">Tamaño máximo del paquete innecesario</string>
<string name="init_packet_junk_size">Tamaño basura del paquete de inicialización</string> <string name="init_packet_junk_size">Tamaño basura del paquete de inicialización</string>
<string name="unsure_how">Si no estás seguro de cómo proceder</string> <string name="unsure_how">Si no estás seguro de cómo proceder</string>
<string name="see_the">Ver la</string> <string name="see_the">Ver la</string>
@@ -152,7 +152,7 @@
<string name="light">Claro</string> <string name="light">Claro</string>
<string name="dark">Oscuro</string> <string name="dark">Oscuro</string>
<string name="use_root_shell_for_wifi">Utilizar el shell root para obtener el nombre del wifi</string> <string name="use_root_shell_for_wifi">Utilizar el shell root para obtener el nombre del wifi</string>
<string name="tunnel_running">Túnel funcionando</string> <string name="tunnel_running">Túnel conectado</string>
<string name="monitoring_state_changes">Monitorizando cambios de estado</string> <string name="monitoring_state_changes">Monitorizando cambios de estado</string>
<string name="dynamic">Dinámico</string> <string name="dynamic">Dinámico</string>
<string name="language">Idioma</string> <string name="language">Idioma</string>
@@ -175,7 +175,7 @@
<string name="splt_tunneling">Túnel dividido</string> <string name="splt_tunneling">Túnel dividido</string>
<string name="pre_up">Pre up</string> <string name="pre_up">Pre up</string>
<string name="post_up">Post up</string> <string name="post_up">Post up</string>
<string name="pre_down">Pre down</string> <string name="pre_down">Antes de la desactivación</string>
<string name="post_down">Post down</string> <string name="post_down">Post down</string>
<string name="quick_actions">Acciones rápidas</string> <string name="quick_actions">Acciones rápidas</string>
<string name="remove_amnezia_compatibility">Eliminar compatibilidad con Amnezia</string> <string name="remove_amnezia_compatibility">Eliminar compatibilidad con Amnezia</string>
@@ -185,4 +185,70 @@
<string name="server_ipv4">Resolución de host IPv4</string> <string name="server_ipv4">Resolución de host IPv4</string>
<string name="prefer_ipv4">Preferir conexión IPv4</string> <string name="prefer_ipv4">Preferir conexión IPv4</string>
<string name="multiple">Múltiple</string> <string name="multiple">Múltiple</string>
<string name="add_from_url">Añadir desde URL</string>
<string name="save">Guardar</string>
<string name="select">Seleccionar</string>
<string name="join_telegram">Únete a la comunidad de Telegram</string>
<string name="join_matrix">Únete a la comunidad de Matrix</string>
<string name="share">Compartir</string>
<string name="update_download_failed">Fallo al descargar actualización.</string>
<string name="licenses">Licencias</string>
<string name="checking_for_update">Comprobando actualización</string>
<string name="select_all">Seleccionar todos</string>
<string name="permission_required">Permiso requerido</string>
<string name="active">Activo</string>
<string name="copy">Copiar</string>
<string name="bio_not_supported">Datos biométricos no admitidos</string>
<string name="export_tunnels_wireguard">Exportar túneles como WireGuard</string>
<string name="export_tunnels_amnezia">Exportar túneles como Amnezia</string>
<string name="wifi_name_template">Activo: %1$s</string>
<string name="add_tunnel">Añadir túnel</string>
<string name="export_logs">Exportar registros almacenados</string>
<string name="inactive">Inactivo</string>
<string name="search">Buscar</string>
<string name="bio_subtitle">Inicia sesión con tu credencial biométrica</string>
<string name="kernel_name_error">error en el nombre del módulo del kernel</string>
<string name="camera_permission_required">Permiso de cámara requerido</string>
<string name="dns_resolve_error">error de resolución DNS</string>
<string name="error_download_failed">Fallo al descargar configuración</string>
<string name="enter_config_url">Introducir URL de configuración</string>
<string name="bio_update_required">Actualización de seguridad biométrica requerida</string>
<string name="delete_logs">Eliminar y limpiar registros</string>
<string name="app_permission_title">Puente de Control WG Tunnel</string>
<string name="dropdown">Menú desplegable</string>
<string name="info">Info</string>
<string name="delete">Eliminar</string>
<string name="export_failed">Exportación fallida</string>
<string name="app_permission_description">Controlar túneles y funcionalidades de auto-túnel</string>
<string name="tunnel_error_template">Túnel fallido con: %1$s</string>
<string name="invalid_config_error">invalid_config_error</string>
<string name="remote_key_template">Clave: %1$s</string>
<string name="config_error">Error de configuración</string>
<string name="auth_error">error no autorizado</string>
<string name="service_running_error">Error de servicio no en ejecución</string>
<string name="status">Estado</string>
<string name="bio_auth_title">Autenticación biométrica</string>
<string name="version_template">Versión: %1$s</string>
<string name="check_for_update">Comprobar actualización</string>
<string name="update_check_failed">Fallo al comprobar la actualización.</string>
<string name="security_template">Seguridad: %1$s</string>
<string name="flavor_template">Sabor: %1$s</string>
<string name="bio_not_created">Datos biométricos no creados</string>
<string name="tunnel_starting">Iniciando túnel</string>
<string name="enable_remote_app_control">Activar control remoto de la app</string>
<string name="export_success">Éxito al exportar</string>
<string name="download">Descargar</string>
<string name="latest_installed">Ya se está ejecutando la última versión.</string>
<string name="update_available">¡Actualización disponible!</string>
<string name="download_and_install">Descargar e instalar</string>
<string name="install_updated_permission">Esta app necesita permiso para instalar actualizaciónes.</string>
<string name="allow">Permitir</string>
<string name="update_check_unsupported">Comprobación de actualización no permitoda en esta compilación.</string>
<string name="darker">Más oscuro</string>
<string name="amoled">AMOLED</string>
<string name="show_qr">Mostrar QR</string>
<string name="amnezia">Amnezia</string>
<string name="wireguard">WireGuard</string>
<string name="done">Hecho</string>
<string name="nothing_here_yet">¡No hay nada aquí de momento!</string>
</resources> </resources>
+228
View File
@@ -0,0 +1,228 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">WG Tunnel</string>
<string name="error_file_extension">See pole .conf või .zip fail</string>
<string name="vpn_channel_name">VPN-i teavituskanal</string>
<string name="turn_off_tunnel">See toiming eeldab, et tunnel pole töös</string>
<string name="prominent_background_location_message">See funktsionaalsus eeldab, et rakendusel on õigus asukoha ja WiFi SSID tuvastamiseks taustal, seda ka siis, kui rakendus on suletud. Lisateavet leiad Privaatsusreeglite lehelt, mille leiad Kasutajatoe vaatest.</string>
<string name="no_tunnels">Ühtegi tunnelit pole veel lisatud!</string>
<string name="tunnels">Tunnelid</string>
<string name="tunnel_mobile_data">Loo tunnel mobiilse andmesidega</string>
<string name="privacy_policy">Vaata privaatsusreegleid</string>
<string name="okay">Sobib</string>
<string name="tunnel_on_ethernet">Loo tunnel kohtvõrgus</string>
<string name="add_tunnels_text">Lisa conf- või zip-failist</string>
<string name="open_file">Ava fail</string>
<string name="add_from_qr">Lisa QR-koodist</string>
<string name="qr_scan">Skaneeri QR-koodi</string>
<string name="tunnel_name">Tunneli nimi</string>
<string name="exclude">Välista</string>
<string name="include">Kaasa</string>
<string name="config_changes_saved">Seadistuse muudatused on salvestatud.</string>
<string name="public_key">Avalik võti</string>
<string name="addresses">Aadressid</string>
<string name="dns_servers">Nimeserverid</string>
<string name="mtu">MTU</string>
<string name="app_permission_description">Halda tunneleid ja tunnelite automaatse käivitamise seadistusi.</string>
<string name="thank_you">Tänud, et kasutad rakendust WG Tunnel!</string>
<string name="allowed_ips">Lubatud IP-aadressid</string>
<string name="endpoint">Otspunkt</string>
<string name="name">Nimi</string>
<string name="always_on_vpn_support">Luba, et VPN on alati sisse lülitatud</string>
<string name="location_services_not_detected">Asukohateenused pole tuvastatavad</string>
<string name="auto_tunneling">Automaatne tunneldus</string>
<string name="vpn_on">VPN on kasutusel</string>
<string name="vpn_off">VPN pole kasutusel</string>
<string name="create_import">Loo nullist</string>
<string name="turn_on_tunnel">Toiming eeldab aktiivse tunneli olemasolu</string>
<string name="licenses">Litsentsid</string>
<string name="update_check_unsupported">Selle rakenduse versiooni puhul pole uuenduste kontrollimine toetatud.</string>
<string name="darker">Tumedam kujundus</string>
<string name="amoled">AMOLED</string>
<string name="show_qr">Näita QR-koodi</string>
<string name="amnezia">Amnezia</string>
<string name="wireguard">WireGuard</string>
<string name="done">Valmis</string>
<string name="download">Laadi alla</string>
<string name="check_for_update">Kontrolli rakenduse uuendusi</string>
<string name="update_check_failed">Uuenduse kontrollimine ei õnnestunud.</string>
<string name="checking_for_update">Kontrollin uuendusi</string>
<string name="latest_installed">Sa juba kasutad viimast versiooni.</string>
<string name="update_download_failed">Uuenduse allalaadimine ei õnnestunud.</string>
<string name="update_available">Uus versioon on saadaval!</string>
<string name="download_and_install">Laadi alla ja paigalda</string>
<string name="permission_required">Õigused on vajalikud</string>
<string name="install_updated_permission">See rakendus vajab uuenduse paigaldamiseks õigusi.</string>
<string name="allow">Luba</string>
<string name="nothing_here_yet">Siin pole veel midagi!</string>
<string name="share">Jaga</string>
<string name="select_all">Vali kõik</string>
<string name="export_success">Eksportimine õnnestus</string>
<string name="inactive">Pole aktiivne</string>
<string name="active">Aktiivne</string>
<string name="status">Olek</string>
<string name="bio_auth_title">Biomeetriline autentimine</string>
<string name="bio_subtitle">Logi sisse kasutades biomeetrilist autentimist</string>
<string name="bio_not_supported">Biomeetriline tuvastamine pole toetatud</string>
<string name="bio_not_created">Biomeetriline tuvastamine pole seadistatud</string>
<string name="bio_update_required">Vajalik on biomeetrilise tuvastamise turvauuendus</string>
<string name="tunnel_starting">Tunnel käivitub</string>
<string name="enable_remote_app_control">Luba rakenduse kaugjuhtimine</string>
<string name="add_from_url">Lisa võrguaadressilt</string>
<string name="enter_config_url">Sisesta seadistuse võrguaadress</string>
<string name="error_download_failed">Seadistuse allalaadimine ei õnnestunud</string>
<string name="save">Salvesta</string>
<string name="search">Otsi</string>
<string name="select">Vali</string>
<string name="join_telegram">Liitu kogukonnaga Telegramis</string>
<string name="join_matrix">Liitu kogukonnaga Matrixis</string>
<string name="add_tunnel">Lisa tunnel</string>
<string name="export_logs">Ekspordi salvestatud logid</string>
<string name="delete_logs">Kustuta ja eemalda logid</string>
<string name="copy">Kopeeri</string>
<string name="info">Teave</string>
<string name="export_tunnels_amnezia">Ekspordi tunnelid Amnezia jaoks</string>
<string name="export_tunnels_wireguard">Ekspordi tunnelid WireGuardi jaoks</string>
<string name="advanced_settings">Täiendavad seadistused</string>
<string name="show_scripts">Näita skripte</string>
<string name="pre_up">Käivituseelne</string>
<string name="post_up">Käivitusjärgne</string>
<string name="pre_down">Sulgemiseelne</string>
<string name="post_down">Sulgemijärgne</string>
<string name="hide_scripts">Peida skriptid</string>
<string name="enable_amnezia_compatibility">Lisa ühilduvus Amnezia teenustega</string>
<string name="remove_amnezia_compatibility">Eemalda ühilduvus Amnezia teenustega</string>
<string name="show_amnezia_properties">Näita Amnezia seadistusi</string>
<string name="hide_amnezia_properties">Peida Amnezia seadistused</string>
<string name="donate">Toeta projekti rahaliselt</string>
<string name="local_logging">Kohalik logimine</string>
<string name="enable_local_logging">Logi andmed nutiseadmes</string>
<string name="add_from_clipboard">Lisa lõikelaualt</string>
<string name="appearance">Välimus</string>
<string name="notifications">Teavitused</string>
<string name="automatic">Automaatne</string>
<string name="light">Hele kujundus</string>
<string name="dark">Tume kujundus</string>
<string name="dynamic">Dünaamiline kujundus</string>
<string name="language">Keel</string>
<string name="display_theme">Kujundus</string>
<string name="interface_">Võrguliides</string>
<string name="private_key">Privaatvõti</string>
<string name="copy_public_key">Kopeeri avalik võti</string>
<string name="base64_key">base64-kodeeringus võti</string>
<string name="comma_separated_list">komadega eraldatud loend</string>
<string name="listen_port">Kuulatav port</string>
<string name="random">(juhuslik)</string>
<string name="optional">(valikuline)</string>
<string name="preshared_key">Eeljagatud võti</string>
<string name="seconds">sekundit</string>
<string name="persistent_keepalive">Pidev elumärksõnum</string>
<string name="cancel">Katkesta</string>
<string name="error_authentication_failed">Autentimine ei õnnestunud</string>
<string name="exclude_lan">Välista kohtvõrgud</string>
<string name="include_lan">Kaasa kohtvõrgud</string>
<string name="dns_resolve_error">nimelahenduse viga</string>
<string name="peer">Partner</string>
<string name="add_peer">Lisa partner</string>
<string name="default_ping_ip">(valikuline, vaikimisi partneri otspunkt)</string>
<string name="rotate_keys">Vaheta võtmeid</string>
<string name="delete_tunnel">Kustuta tunnel</string>
<string name="delete_tunnel_message">Kas sa oled kindel, et soovid selle tunneli kustutada?</string>
<string name="yes">Jah</string>
<string name="all">kõik</string>
<string name="no_email_detected">E-posti rakendust ei õnnestu tuvastada</string>
<string name="no_browser_detected">Veebibrauserit ei õnnestu tuvastada</string>
<string name="open_issue">Alusta veateate koostamist</string>
<string name="read_logs">Loe logisid</string>
<string name="auto">(automaatne)</string>
<string name="incorrect_pin">PIN-kood pole õige</string>
<string name="pin_created">PIN-koodi loomine õnnestus</string>
<string name="enter_pin">Sisesta oma PIN-kood</string>
<string name="create_pin">Loo PIN-kood</string>
<string name="edit_tunnel">Muuda tunnelit</string>
<string name="version">Versioon</string>
<string name="settings">Seadistused</string>
<string name="support">Kasutajatugi</string>
<string name="unknown_error">Tekkis tundmatu viga</string>
<string name="tunnel_on_wifi">Loo tunnel ebausaldusväärses WiFi võrgus</string>
<string name="email_subject">WG Tunneli kasutajatugi</string>
<string name="email_chooser">Saada e-kiri…</string>
<string name="docs_description">Loe dokumentatsiooni</string>
<string name="email_description">Saada mulle e-kiri</string>
<string name="use_kernel">Kasuta kernelimoodulit</string>
<string name="error_ssid_exists">SSID on juba olemas</string>
<string name="error_root_denied">Juurkasutaja õigustes kest on keelatud</string>
<string name="error_no_file_explorer">Failihaldurit pole paigaldatud</string>
<string name="set_primary_tunnel">Määra põhiliseks tunneliks</string>
<string name="skip">Jäta vahele</string>
<string name="export_failed">Eksportimine ei õnnestunud</string>
<string name="tunnel_error_template">Viga tunneli töös: %1$s</string>
<string name="wifi_name_template">Aktiivne: %1$s</string>
<string name="remote_key_template">Võti: %1$s</string>
<string name="version_template">Versioon: %1$s</string>
<string name="security_template">Turvalisus: %1$s</string>
<string name="flavor_template">Levitusviis: %1$s</string>
<string name="config_error">seadistusviga</string>
<string name="invalid_config_error">vigane_seadistus_viga</string>
<string name="kernel_name_error">viga kerneli mooduli nimes</string>
<string name="auth_error">viga autentimisel</string>
<string name="service_running_error">viga, kus teenus ei toimi</string>
<string name="unsure_how">kui sa ei tea, mida järgmiseks teha</string>
<string name="see_the">Vaata</string>
<string name="getting_started_guide">esimeste toimingute juhendit</string>
<string name="restart_at_boot">Käivita alglaadimisel uuesti</string>
<string name="vpn_denied_dialog_title">Õigused on puudu</string>
<string name="set_custom_ping_internal">Pingi välp (sekundites)</string>
<string name="optional_default">"valikuline, vaikimisi: "</string>
<string name="never">mitte kunagi</string>
<string name="sec">sek</string>
<string name="handshake">kätlus</string>
<string name="logs">Logid</string>
<string name="kill_switch">Kiirpeatamine</string>
<string name="trusted_wifi_names">Usaldusväärsete WiFi-võrkude nimed</string>
<string name="add_wifi_name">Lisa WiFi võrgunimi</string>
<string name="primary_tunnel">Põhiline tunnel</string>
<string name="app_settings">rakenduse seadistused</string>
<string name="background_location_message2">tagamaks, et need õigused on lubatud</string>
<string name="root_accepted">Juurkasutaja kest on lubatud</string>
<string name="set_custom_ping_ip">Sisesta muu ip-aadress</string>
<string name="learn_more">Lisateave</string>
<string name="monitoring_state_changes">Jälgin oleku muudatusi</string>
<string name="tunnel_running">Tunnel töötab</string>
<string name="tunnel_specific_settings">Tunnelikohased seadistused</string>
<string name="kernel_not_supported">Kernel pole toetatud</string>
<string name="start_auto">Käivita automaatne tunneldus</string>
<string name="stop_auto">Peata automaatne tunneldus</string>
<string name="quick_actions">Kiirtoimingud</string>
<string name="tunnel_control">Tunneli juhtimine</string>
<string name="auto_tunnel">Automaatne tunneldus</string>
<string name="delete">Kustuta</string>
<string name="camera_permission_required">Vajalik on õigus kasutada kaamerat</string>
<string name="dropdown">Rippmenüü</string>
<string name="splt_tunneling">Jagatud tunneldus</string>
<string name="stop">peata</string>
<string name="stop_on_no_internet">Peata internetiühenduse puudumisel</string>
<string name="stop_on_internet_loss">Peata tunnel internetiühenduse kadumisel</string>
<string name="enable_app_lock">Kasuta rakenduse lukustust</string>
<string name="restart_on_ping">Pingimise mittetoimimisel käivita uuesti (beeta)</string>
<string name="mobile_data_tunnel">Määra mobiilse andmeside tunneliks</string>
<string name="use_tunnel_on_wifi_name">Kasuta tunnelit WiFi nime puhul</string>
<string name="kernel">Tuum/Kernel</string>
<string name="error_file_format">Vigane tunneli seadistuste vorming</string>
<string name="mobile_tunnel">Mobiilside andmetunnel</string>
<string name="launch_app_settings">Käivita rakenduse seadistused</string>
<string name="use_wildcards">Kasuta nimedes metamärke</string>
<string name="wildcards_active">Metamärgid on kasutusel</string>
<string name="prefer_ipv4">Eelista IPv4 ühendust</string>
<string name="server_ipv4">IPv4 hostinime nimelahendus</string>
<string name="junk_packet_count">Rämpspakettide arv</string>
<string name="junk_packet_minimum_size">Rämpspaketi miinimumsuurus</string>
<string name="junk_packet_maximum_size">Rämpspaketi maksimumsuurus</string>
<string name="init_packet_junk_size">Alustava paketi rämpsuosa suurus</string>
<string name="response_packet_junk_size">Vastuspaketi rämpsuosa suurus</string>
<string name="init_packet_magic_header">Alustava paketi päise kohandatud osa</string>
<string name="response_packet_magic_header">Vastuspaketi päise kohandatud osa</string>
<string name="transport_packet_magic_header">Transpordipaketi päise kohandatud osa</string>
<string name="underload_packet_magic_header">Väikese koormusega paketi päise kohandatud osa</string>
<string name="vpn_settings">VPN-i süsteemsed seadistused</string>
</resources>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_permission_title">پل کنترل تونل وایرگارد</string>
<string name="privacy_policy">مشاهده سیاست حفظ حریم خصوصی</string>
<string name="vpn_channel_name">کانال اطلاع رسانی VPN</string>
<string name="error_file_extension">فایل .conf یا .zip نیست.</string>
<string name="turn_off_tunnel">اقدام نیاز به تونل زنی دارد</string>
<string name="no_tunnels">هنوز تونلی اضافه نشده!</string>
<string name="tunnels">تونل‌ها</string>
<string name="tunnel_mobile_data">تونل روی داده تلفن همراه</string>
<string name="okay">باشه</string>
<string name="prominent_background_location_title">افشای موقعیت مکانی پس‌زمینه</string>
<string name="thank_you">از استفاده شما از تونل WG متشکریم!</string>
<string name="trusted_ssid_value_description">ارسال SSID</string>
<string name="add_tunnels_text">از فایل یا زیپ اضافه کنید</string>
<string name="app_permission_description">تونل‌های کنترل و ویژگی‌های تونل خودکار.</string>
<string name="tunnel_on_ethernet">تونل روی اترنت</string>
<string name="prominent_background_location_message">این ویژگی برای فعال کردن نظارت بر SSID وای‌فای، حتی در زمان بسته بودن برنامه، به مجوز موقعیت مکانی در پس‌زمینه نیاز دارد. برای جزئیات بیشتر، لطفاً به سیاست حفظ حریم خصوصی که در صفحه پشتیبانی لینک شده است، مراجعه کنید.</string>
<string name="app_name">WG Tunnel</string>
</resources>
+12
View File
@@ -115,4 +115,16 @@
<string name="sec">sek</string> <string name="sec">sek</string>
<string name="read_logs">Lue lokitiedot</string> <string name="read_logs">Lue lokitiedot</string>
<string name="mobile_tunnel">Mobiilidatatunneli</string> <string name="mobile_tunnel">Mobiilidatatunneli</string>
<string name="restart_at_boot">Käynnistä laitteen käynnistyksen yhteydessä</string>
<string name="always_on_message2">varmistaaksesi, että Aina päällä oleva VPN on kytketty pois päältä kaikkien muiden sovellusten osalta ja yritä uudelleen</string>
<string name="background_location_message">Tätä toimintoa varten tulee sijainnin käyttölupa olla aina sallittuna ja/tai tarkka sijainti käytössä. Katso</string>
<string name="app_settings">sovelluksen asetukset</string>
<string name="root_accepted">Root hyväksytty</string>
<string name="set_custom_ping_ip">Määrittele pingin ip-osoite</string>
<string name="set_custom_ping_internal">Pingin aikaväli (sek)</string>
<string name="show_amnezia_properties">Näytä Amnezia-asetukset</string>
<string name="launch_app_settings">Käynnistä sovelluksen asetukset</string>
<string name="use_wildcards">Käytä jokerimerkkiä nimissä</string>
<string name="wildcards_active">Jokerimerkit aktivoitu</string>
<string name="tunnel_running">Tunneli käytössä</string>
</resources> </resources>
+35
View File
@@ -171,4 +171,39 @@
<string name="quick_actions">Actions rapides</string> <string name="quick_actions">Actions rapides</string>
<string name="enable_amnezia_compatibility">Activer la prise en charge d\'Amnezia</string> <string name="enable_amnezia_compatibility">Activer la prise en charge d\'Amnezia</string>
<string name="include_lan">Inclure le LAN</string> <string name="include_lan">Inclure le LAN</string>
<string name="select">Sélectionner</string>
<string name="join_telegram">Rejoindre la communauté Telegram</string>
<string name="join_matrix">Rejoindre la communauté Matrix</string>
<string name="auto_tunnel_channel_description">Un canal pour les notifications de l\'état du tunnel automatique</string>
<string name="tunnel_control">Contrôle du tunnel</string>
<string name="auto_tunnel">Tunnel automatique</string>
<string name="add_tunnel">Ajouter un tunnel</string>
<string name="error_download_failed">Le téléchargement de la configuration a échouée</string>
<string name="multiple">Multiple</string>
<string name="add_from_url">Ajouter depuis un URL</string>
<string name="enter_config_url">Saisissez l\'URL de configuration</string>
<string name="search">Rechercher</string>
<string name="save">Sauvegarder</string>
<string name="copy">Copier</string>
<string name="info">Informations</string>
<string name="prefer_ipv4">Préférer une connexion IPv4</string>
<string name="allow">Autoriser</string>
<string name="app_permission_title">Pont de contrôle du tunnel WG</string>
<string name="app_permission_description">Contrôler les tunnels et les fonctions automatiques des tunnels.</string>
<string name="select">Sélectionner</string>
<string name="join_telegram">Rejoindre la communauté Telegram</string>
<string name="join_matrix">Rejoindre la communauté Matrix</string>
<string name="auto_tunnel_channel_description">Un canal pour les notifications de l\'état du tunnel automatique</string>
<string name="tunnel_control">Contrôle du tunnel</string>
<string name="auto_tunnel">Tunnel automatique</string>
<string name="add_tunnel">Ajouter un tunnel</string>
<string name="error_download_failed">Le téléchargement de la configuration a échouée</string>
<string name="multiple">Multiple</string>
<string name="add_from_url">Ajouter depuis un URL</string>
<string name="enter_config_url">Saisissez l\'URL de configuration</string>
<string name="search">Rechercher</string>
<string name="save">Sauvegarder</string>
<string name="copy">Copier</string>
<string name="info">Informations</string>
<string name="prefer_ipv4">Préférer une connexion IPv4</string>
</resources> </resources>
+8 -1
View File
@@ -1,2 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="app_name">WG Tunnel</string>
</resources>
<resources>
<string name="app_name">WG Tunnel</string>
<string name="app_permission_description">Alagutak és automatikus alagút funkciók vezérlése.</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More