Compare commits

...

35 commits

Author SHA1 Message Date
Víctor Assunção
bd61fe6748 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (51 of 51 strings)

Co-authored-by: Víctor Assunção <joaovictor.jvas@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/pt_BR/
Translation: PrivateDNSAndroid/Private DNS Quick Toggle
2025-02-25 13:29:40 +03:00
Michal L
87fe66af20 Translated using Weblate (Polish)
Currently translated at 100.0% (51 of 51 strings)

Co-authored-by: Michal L <michalrmsmi@wp.pl>
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/pl/
Translation: PrivateDNSAndroid/Private DNS Quick Toggle
2025-02-25 13:29:40 +03:00
தமிழ்நேரம்
c375776575 Translated using Weblate (Tamil)
Currently translated at 100.0% (51 of 51 strings)

Added translation using Weblate (Tamil)

Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/ta/
Translation: PrivateDNSAndroid/Private DNS Quick Toggle
2025-02-25 13:29:40 +03:00
Mustafa A
3b24d24ba4 Translated using Weblate (Turkish)
Currently translated at 98.0% (50 of 51 strings)

Added translation using Weblate (Turkish)

Co-authored-by: Mustafa A <musty_99@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/tr/
Translation: PrivateDNSAndroid/Private DNS Quick Toggle
2025-02-25 13:29:40 +03:00
ajan
e8885409b5 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (51 of 51 strings)

Co-authored-by: ajan <ajan.ib.rown969@googlemail.com>
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/pt_BR/
Translation: PrivateDNSAndroid/Private DNS Quick Toggle
2025-02-25 13:29:40 +03:00
Maksim Karasev
2aa895cd5e Add 1.10.0 changelog 2025-02-25 13:29:11 +03:00
Maksim Karasev
bdd98109ed Update layout for Android 15 2025-02-25 13:21:09 +03:00
Maksim Karasev
3b09f605f9 Bump dependencies 2025-02-25 11:27:46 +03:00
papaindiatango
9d62e91b60 Translated using Weblate (French)
Currently translated at 90.1% (46 of 51 strings)

Translation: PrivateDNSAndroid/Private DNS Quick Toggle
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/fr/
2025-01-18 18:55:27 +03:00
tuấn nguyễn
e155d17dd7 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (49 of 49 strings)

Translation: PrivateDNSAndroid/Private DNS Quick Toggle
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/vi/
2025-01-17 16:00:25 +03:00
tuấn nguyễn
e2104952bc Added translation using Weblate (Vietnamese) 2025-01-17 16:00:25 +03:00
ajan
24800f7f2d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (49 of 49 strings)

Translation: PrivateDNSAndroid/Private DNS Quick Toggle
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/pt_BR/
2025-01-17 16:00:25 +03:00
ajan
6a5f2af6f6 Added translation using Weblate (Portuguese (Brazil)) 2025-01-17 16:00:25 +03:00
Michal L
0e0e0bf9b4 Translated using Weblate (Polish)
Currently translated at 100.0% (49 of 49 strings)

Translation: PrivateDNSAndroid/Private DNS Quick Toggle
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/pl/
2025-01-17 16:00:25 +03:00
Michal L
4c6240bd34 Translated using Weblate (Polish)
Currently translated at 71.4% (35 of 49 strings)

Translation: PrivateDNSAndroid/Private DNS Quick Toggle
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/pl/
2025-01-17 16:00:25 +03:00
Michal L
e4b9e84f8c Translated using Weblate (Polish)
Currently translated at 67.3% (33 of 49 strings)

Translation: PrivateDNSAndroid/Private DNS Quick Toggle
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/pl/
2025-01-17 16:00:25 +03:00
Eryk Michalak
a379c81cb9 Translated using Weblate (Polish)
Currently translated at 59.1% (29 of 49 strings)

Translation: PrivateDNSAndroid/Private DNS Quick Toggle
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/pl/
2025-01-17 16:00:25 +03:00
Michal L
6164a35f04 Translated using Weblate (Polish)
Currently translated at 59.1% (29 of 49 strings)

Translation: PrivateDNSAndroid/Private DNS Quick Toggle
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/pl/
2025-01-17 16:00:25 +03:00
Michal L
e4498ca64a Added translation using Weblate (Polish) 2025-01-17 16:00:25 +03:00
Purevbaatar Tuvshinjargal
75413fddcd Translated using Weblate (Mongolian)
Currently translated at 93.8% (46 of 49 strings)

Translation: PrivateDNSAndroid/Private DNS Quick Toggle
Translate-URL: https://hosted.weblate.org/projects/privatednsandroid/private-dns-quick-toggle/mn/
2025-01-17 16:00:25 +03:00
Purevbaatar Tuvshinjargal
6cc99b7820 Added translation using Weblate (Mongolian) 2025-01-17 16:00:25 +03:00
Maksim Karasev
efd48b8984 Add support for Shizuku on Android 14 QPR3+ (#45) 2025-01-17 15:56:15 +03:00
Maksim Karasev
b39d7e3624 Update .gitignore 2025-01-17 15:52:24 +03:00
Maksim Karasev
4f6dc13c12 Fix miscellaneous bugs and simplify code 2025-01-17 15:50:14 +03:00
Maksim Karasev
5ba03acbcc Enable Strict Mode for debug builds 2025-01-17 15:44:32 +03:00
Maksim Karasev
627771d4b1 Migrate build config to Kotlin 2025-01-17 15:38:40 +03:00
Pacuka
8c7ff2ca8f
Add files via upload (#43)
Hungarian translation by Pacuka
2024-12-17 13:13:46 +03:00
Maksim Karasev
402b084954 Include Contributing section in the readme 2024-11-17 12:57:43 +03:00
Maksim Karasev
8c927d6b26 Remove region from RU locale 2024-11-06 14:16:03 +03:00
Maksim Karasev
7cdc2bbb84 Bump version 2024-11-05 15:59:41 +03:00
Maksim Karasev
835e9381ea Add Russian translation 2024-11-05 15:54:07 +03:00
Maksim Karasev
14b320ac68 Enable per-app language support 2024-11-05 15:53:22 +03:00
Maksim Karasev
681e6ceef4 Reset provider when disabling through the dialog 2024-10-03 17:10:23 +03:00
Maksim Karasev
6a5f405211 Replace gson with kotlinx-serialization 2024-10-03 16:38:23 +03:00
Weiguangtwk
fa9d259a21
Add Chinese Simplified Translation (#32)
* Add Chinese Simplified Translation

* Add new translation

---------

Co-authored-by: WeiguangTWK <weiguangtwk@outlook.com>
2024-09-24 13:43:47 +03:00
33 changed files with 896 additions and 254 deletions

5
.gitignore vendored
View file

@ -67,3 +67,8 @@ fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# kotlin
.kotlin/
*~

2
.idea/compiler.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
<bytecodeTargetLevel target="21" />
</component>
</project>

2
.idea/kotlinc.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.10" />
<option name="version" value="2.0.20" />
</component>
</project>

View file

@ -1,6 +1,7 @@
[![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/karasevm/PrivateDNSAndroid/total)](https://github.com/karasevm/PrivateDNSAndroid/releases/latest)
[![GitHub Release](https://img.shields.io/github/v/release/karasevm/PrivateDNSAndroid)](https://github.com/karasevm/PrivateDNSAndroid/releases/latest)
[![IzzyOnDroid](https://img.shields.io/endpoint?url=https://apt.izzysoft.de/fdroid/api/v1/shield/ru.karasevm.privatednstoggle&label=IzzyOnDroid)](https://apt.izzysoft.de/fdroid/index/apk/ru.karasevm.privatednstoggle)
[![Translation status](https://hosted.weblate.org/widget/privatednsandroid/private-dns-quick-toggle/svg-badge.svg)](https://hosted.weblate.org/engage/privatednsandroid/)
# Private DNS Quick Toggle
A quick settings tile to switch your private dns provider. Supports any number of providers. Makes it easy to turn adblocking dns servers on or off with just
@ -12,11 +13,11 @@ a single tap.
Get the latest apk on the [releases page](https://github.com/karasevm/PrivateDNSAndroid/releases/latest)
or from [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/index/apk/ru.karasevm.privatednstoggle).
## Automatic (Shizuku)
### Automatic (Shizuku)
1. Install and start [Shizuku](https://shizuku.rikka.app/).
2. Start the app and allow Shizuku access when prompted.
## Manual
### Manual
For the app to work properly you'll need to provide it permissions via ADB:
1. Get to your PC and download platform tools from google [here](https://developer.android.com/studio/releases/platform-tools).
@ -31,3 +32,18 @@ For the app to work properly you'll need to provide it permissions via ADB:
6. That's it, you should have the app installed.
## Contributing
### Translation
The easiest way to contribute would be to submit a translation to your language. Thanks to Weblate gratis hosting for open-source projects you can do it without any programming knowledge on [their website](https://hosted.weblate.org/engage/privatednsandroid/).
#### Translation status
<a href="https://hosted.weblate.org/engage/privatednsandroid/">
<img src="https://hosted.weblate.org/widget/privatednsandroid/private-dns-quick-toggle/multi-auto.svg" alt="Translation status" />
</a>
### Code
If you want to contribute code please try to adhere to the following guidelines:
- Include javadoc comments for all the public methods you add
- Keep the code neatly formatted, you can you the built-in Android Studio formatter
- Please describe what your code does and how does it do that when sending a PR
- Before sending a PR please test your change on the oldest and latest supported Android versions (9 and 14 at the time of writing)

View file

@ -1,82 +0,0 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.devtools.ksp'
}
android {
compileSdk 34
defaultConfig {
applicationId "ru.karasevm.privatednstoggle"
versionCode 16
versionName "1.9.0-beta1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
targetSdkVersion 34
minSdkVersion 28
}
buildFeatures {
viewBinding true
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".dev"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
namespace 'ru.karasevm.privatednstoggle'
}
dependencies {
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.activity:activity-ktx:1.9.2'
implementation 'androidx.fragment:fragment-ktx:1.8.3'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.guava:guava:33.1.0-android'
implementation 'com.google.code.gson:gson:2.11.0'
def shizuku_version = '13.1.5'
implementation "dev.rikka.shizuku:api:$shizuku_version"
implementation "dev.rikka.shizuku:provider:$shizuku_version"
implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3'
compileOnly 'dev.rikka.hidden:stub:4.3.2'
// Room components
def roomVersion = '2.6.1'
implementation "androidx.room:room-ktx:$roomVersion"
ksp "androidx.room:room-compiler:$roomVersion"
androidTestImplementation "androidx.room:room-testing:$roomVersion"
// Lifecycle components
def lifecycleVersion = '2.8.5'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}

88
app/build.gradle.kts Normal file
View file

@ -0,0 +1,88 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
compileSdk = 35
androidResources {
generateLocaleConfig = true
}
defaultConfig {
applicationId = "ru.karasevm.privatednstoggle"
versionCode = 18
versionName = "1.10.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
targetSdk = 35
minSdk = 28
}
buildFeatures {
viewBinding = true
buildConfig = true
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
debug {
applicationIdSuffix = ".dev"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
namespace = "ru.karasevm.privatednstoggle"
}
dependencies {
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.recyclerview:recyclerview:1.4.0")
implementation("androidx.activity:activity-ktx:1.10.0")
implementation("androidx.fragment:fragment-ktx:1.8.6")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
implementation("com.google.guava:guava:33.1.0-android")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
val shizukuVersion = "13.1.5"
implementation("dev.rikka.shizuku:api:$shizukuVersion")
implementation("dev.rikka.shizuku:provider:$shizukuVersion")
compileOnly("dev.rikka.hidden:stub:4.3.3")
implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
// Room components
val roomVersion = "2.6.1"
implementation("androidx.room:room-ktx:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")
androidTestImplementation("androidx.room:room-testing:$roomVersion")
// Lifecycle components
val lifecycleVersion = "2.8.7"
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
}

View file

@ -1,6 +1,7 @@
package ru.karasevm.privatednstoggle
import android.app.Application
import android.os.StrictMode
import com.google.android.material.color.DynamicColors
import ru.karasevm.privatednstoggle.data.DnsServerRepository
import ru.karasevm.privatednstoggle.data.database.DnsServerRoomDatabase
@ -13,5 +14,20 @@ class PrivateDNSApp : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this)
if (BuildConfig.DEBUG){
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
}
}
}

View file

@ -3,19 +3,21 @@ package ru.karasevm.privatednstoggle.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
// All fields must have default values for proper deserialization
@Serializable
@Entity(tableName = "dns_servers")
data class DnsServer(
@SerializedName("id")
@SerialName("id")
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@SerializedName("server")
@SerialName("server")
val server: String = "",
@SerializedName("label")
@SerialName("label")
val label: String = "",
@SerializedName("enabled")
@SerialName("enabled")
@ColumnInfo(defaultValue = "1")
val enabled: Boolean = true,
val sortOrder: Int? = null

View file

@ -8,6 +8,7 @@ import android.graphics.drawable.Icon
import android.provider.Settings
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.util.Log
import androidx.core.content.ContextCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -35,6 +36,8 @@ class DnsTileService : TileService() {
private val repository: DnsServerRepository by lazy { (application as PrivateDNSApp).repository }
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
private val sharedPreferences by lazy { PreferenceHelper.defaultPreference(this) }
private var isBroadcastReceiverRegistered = false
override fun onTileAdded() {
super.onTileAdded()
@ -53,9 +56,8 @@ class DnsTileService : TileService() {
val dnsMode = Settings.Global.getString(contentResolver, "private_dns_mode")
val dnsProvider = Settings.Global.getString(contentResolver, "private_dns_specifier")
val sharedPrefs = PreferenceHelper.defaultPreference(this)
if (dnsMode.equals(DNS_MODE_OFF, ignoreCase = true)) {
if (sharedPrefs.autoMode == AUTO_MODE_OPTION_AUTO || sharedPrefs.autoMode == AUTO_MODE_OPTION_OFF_AUTO) {
if (sharedPreferences.autoMode == AUTO_MODE_OPTION_AUTO || sharedPreferences.autoMode == AUTO_MODE_OPTION_OFF_AUTO) {
changeDNSServer(DNS_MODE_AUTO, dnsProvider)
} else {
changeDNSServer(DNS_MODE_PRIVATE, dnsProvider)
@ -66,10 +68,10 @@ class DnsTileService : TileService() {
} else if (dnsMode.equals(DNS_MODE_PRIVATE, ignoreCase = true)) {
scope.launch {
if (getNextAddress(dnsProvider) == null) {
if (sharedPrefs.autoMode == AUTO_MODE_OPTION_PRIVATE) {
if (sharedPreferences.autoMode == AUTO_MODE_OPTION_PRIVATE) {
changeDNSServer(DNS_MODE_PRIVATE, null)
} else {
if (sharedPrefs.autoMode == AUTO_MODE_OPTION_AUTO) {
if (sharedPreferences.autoMode == AUTO_MODE_OPTION_AUTO) {
changeDNSServer(DNS_MODE_AUTO, dnsProvider)
} else {
changeDNSServer(DNS_MODE_OFF, dnsProvider)
@ -134,10 +136,9 @@ class DnsTileService : TileService() {
if (!checkForPermission(this)) {
return
}
val sharedPrefs = PreferenceHelper.defaultPreference(this)
// Require unlock to change mode according to user preference
val requireUnlock = sharedPrefs.requireUnlock
val requireUnlock = sharedPreferences.requireUnlock
if (isLocked && requireUnlock) {
unlockAndRun(this::cycleState)
} else {
@ -151,12 +152,13 @@ class DnsTileService : TileService() {
* Refreshes the state of the tile
*/
private fun refreshTile() {
val isPermissionGranted = checkForPermission(this)
val dnsMode = Settings.Global.getString(contentResolver, "private_dns_mode")
when (dnsMode?.lowercase()) {
DNS_MODE_OFF -> {
setTile(
qsTile,
Tile.STATE_INACTIVE,
if (!isPermissionGranted) Tile.STATE_UNAVAILABLE else Tile.STATE_INACTIVE,
getString(R.string.dns_off),
R.drawable.ic_off_black_24dp
)
@ -165,7 +167,7 @@ class DnsTileService : TileService() {
DNS_MODE_AUTO -> {
setTile(
qsTile,
Tile.STATE_INACTIVE,
if (!isPermissionGranted) Tile.STATE_UNAVAILABLE else Tile.STATE_INACTIVE,
getString(R.string.dns_auto),
R.drawable.ic_auto_black_24dp
)
@ -178,7 +180,7 @@ class DnsTileService : TileService() {
val dnsServer = repository.getFirstByServer(activeAddress)
setTile(
qsTile,
Tile.STATE_ACTIVE,
if (!isPermissionGranted) Tile.STATE_UNAVAILABLE else Tile.STATE_ACTIVE,
// display server address if either there is no label or the server is not known
dnsServer?.label?.ifBlank { activeAddress } ?: activeAddress,
R.drawable.ic_private_black_24dp
@ -189,7 +191,7 @@ class DnsTileService : TileService() {
else -> {
setTile(
qsTile,
Tile.STATE_INACTIVE,
if (!isPermissionGranted) Tile.STATE_UNAVAILABLE else Tile.STATE_INACTIVE,
getString(R.string.dns_unknown),
R.drawable.ic_unknown_black_24dp
)
@ -205,12 +207,10 @@ class DnsTileService : TileService() {
override fun onStartListening() {
super.onStartListening()
if (!checkForPermission(this)) {
return
}
// Prevent some crashes
if (qsTile == null) {
Log.w(TAG, "onStartListening: qsTile is null")
return
}
@ -222,14 +222,17 @@ class DnsTileService : TileService() {
IntentFilter("refresh_tile"),
ContextCompat.RECEIVER_NOT_EXPORTED
)
isBroadcastReceiverRegistered = true
refreshTile()
}
override fun onStopListening() {
super.onStopListening()
unregisterReceiver(broadcastReceiver)
if (isBroadcastReceiverRegistered) {
unregisterReceiver(broadcastReceiver)
isBroadcastReceiverRegistered = false
}
}
override fun onDestroy() {
@ -292,4 +295,8 @@ class DnsTileService : TileService() {
repository.getNextByServer(currentAddress)
}
}
}
companion object {
private const val TAG = "DnsTileService"
}
}

View file

@ -17,6 +17,7 @@ import ru.karasevm.privatednstoggle.data.DnsServerViewModelFactory
import ru.karasevm.privatednstoggle.databinding.SheetDnsSelectorBinding
import ru.karasevm.privatednstoggle.model.DnsServer
import ru.karasevm.privatednstoggle.util.PrivateDNSUtils
import ru.karasevm.privatednstoggle.util.PrivateDNSUtils.checkForPermission
class DNSServerDialogFragment : DialogFragment() {
@ -27,7 +28,7 @@ class DNSServerDialogFragment : DialogFragment() {
private lateinit var adapter: ServerListRecyclerAdapter
private var servers: MutableList<DnsServer> = mutableListOf()
private val dnsServerViewModel: DnsServerViewModel by viewModels { DnsServerViewModelFactory((requireActivity().application as PrivateDNSApp).repository) }
private val contentResolver by lazy { requireActivity().contentResolver }
override fun onCreateDialog(
savedInstanceState: Bundle?
@ -68,22 +69,33 @@ class DNSServerDialogFragment : DialogFragment() {
override fun onStart() {
super.onStart()
if (!checkForPermission(requireContext())) {
Toast.makeText(
context, R.string.permission_missing, Toast.LENGTH_SHORT
).show()
dialog!!.dismiss()
}
adapter.onItemClick = { id ->
when (id) {
OFF_ID -> {
PrivateDNSUtils.setPrivateMode(
requireActivity().contentResolver,
contentResolver,
PrivateDNSUtils.DNS_MODE_OFF
)
PrivateDNSUtils.setPrivateProvider(
contentResolver,
null)
Toast.makeText(context, R.string.set_to_off_toast, Toast.LENGTH_SHORT).show()
}
AUTO_ID -> {
PrivateDNSUtils.setPrivateMode(
requireActivity().contentResolver,
contentResolver,
PrivateDNSUtils.DNS_MODE_AUTO
)
PrivateDNSUtils.setPrivateProvider(
contentResolver,
null)
Toast.makeText(context, R.string.set_to_auto_toast, Toast.LENGTH_SHORT).show()
}
@ -91,11 +103,11 @@ class DNSServerDialogFragment : DialogFragment() {
lifecycleScope.launch {
val server = servers.find { server -> server.id == id }
PrivateDNSUtils.setPrivateMode(
requireActivity().contentResolver,
contentResolver,
PrivateDNSUtils.DNS_MODE_PRIVATE
)
PrivateDNSUtils.setPrivateProvider(
requireActivity().contentResolver,
contentResolver,
server?.server
)
Toast.makeText(

View file

@ -6,13 +6,11 @@ import android.content.ClipDescription.MIMETYPE_TEXT_PLAIN
import android.content.ClipboardManager
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.IPackageManager
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.permission.IPermissionManager
import android.util.Log
import android.view.Menu
import android.view.View
@ -27,17 +25,11 @@ import androidx.recyclerview.widget.ItemTouchHelper.DOWN
import androidx.recyclerview.widget.ItemTouchHelper.UP
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonSyntaxException
import com.google.gson.ToNumberPolicy
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.launch
import org.lsposed.hiddenapibypass.HiddenApiBypass
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.ShizukuProvider
import rikka.shizuku.SystemServiceHelper
import ru.karasevm.privatednstoggle.PrivateDNSApp
import ru.karasevm.privatednstoggle.R
import ru.karasevm.privatednstoggle.data.DnsServerViewModel
@ -47,6 +39,7 @@ import ru.karasevm.privatednstoggle.model.DnsServer
import ru.karasevm.privatednstoggle.util.BackupUtils
import ru.karasevm.privatednstoggle.util.PreferenceHelper
import ru.karasevm.privatednstoggle.util.PreferenceHelper.dns_servers
import ru.karasevm.privatednstoggle.util.ShizukuUtil.grantPermissionWithShizuku
class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogListener,
@ -54,11 +47,9 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
private lateinit var linearLayoutManager: LinearLayoutManager
private lateinit var binding: ActivityMainBinding
private var items = mutableListOf<String>()
private lateinit var sharedPrefs: SharedPreferences
private lateinit var adapter: ServerListRecyclerAdapter
private lateinit var clipboard: ClipboardManager
private lateinit var gson: Gson
private val dnsServerViewModel: DnsServerViewModel by viewModels { DnsServerViewModelFactory((application as PrivateDNSApp).repository) }
private val itemTouchHelper by lazy {
@ -126,7 +117,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
private fun importSettings(json: String) {
runCatching {
val data: BackupUtils.Backup = gson.fromJson(json, BackupUtils.Backup::class.java)
val data: BackupUtils.Backup = Json.decodeFromString<BackupUtils.Backup>(json)
BackupUtils.import(data, dnsServerViewModel, sharedPrefs)
}.onSuccess {
Toast.makeText(
@ -134,28 +125,18 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
).show()
}.onFailure { exception ->
runCatching {
val objectType = object : TypeToken<Map<String, Any>>() {}.type
val data: Map<String, Any> = gson.fromJson(json, objectType)
Log.e("IMPORT", "Malformed json, falling back to legacy", exception)
val data = Json.decodeFromString<BackupUtils.LegacyBackup>(json)
BackupUtils.importLegacy(data, dnsServerViewModel, sharedPrefs)
}.onSuccess {
Toast.makeText(
this, getString(R.string.import_success), Toast.LENGTH_SHORT
).show()
}.onFailure {
}.onFailure { exception ->
Log.e("IMPORT", "Import failed", exception)
when (exception) {
is JsonSyntaxException -> {
Toast.makeText(
this, getString(R.string.import_failure_json), Toast.LENGTH_SHORT
).show()
}
else -> {
Toast.makeText(
this, getString(R.string.import_failure), Toast.LENGTH_SHORT
).show()
}
}
Toast.makeText(
this, getString(R.string.import_failure), Toast.LENGTH_SHORT
).show()
}
}
}
@ -164,18 +145,20 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
* Migrate the SharedPreferences server list to Room
*/
private fun migrateServerList() {
if (sharedPrefs.dns_servers.isNotEmpty() && sharedPrefs.dns_servers[0] != "") {
Log.i(
"migrate",
"existing sharedPrefs list: ${sharedPrefs.dns_servers} ${sharedPrefs.dns_servers.size}"
)
sharedPrefs.dns_servers.forEach { server ->
val parts = server.split(" : ").toMutableList()
if (parts.size != 2) parts.add(0, "")
Log.i("migrate", "migrating: $server -> $parts")
dnsServerViewModel.insert(DnsServer(0, parts[1], parts[0]))
dnsServerViewModel.viewModelScope.launch {
if (sharedPrefs.dns_servers.isNotEmpty() && sharedPrefs.dns_servers[0] != "") {
Log.i(
"migrate",
"existing sharedPrefs list: ${sharedPrefs.dns_servers} ${sharedPrefs.dns_servers.size}"
)
sharedPrefs.dns_servers.forEach { server ->
val parts = server.split(" : ").toMutableList()
if (parts.size != 2) parts.add(0, "")
Log.i("migrate", "migrating: $server -> $parts")
dnsServerViewModel.insert(DnsServer(0, parts[1], parts[0]))
}
sharedPrefs.dns_servers = emptyList<String>().toMutableList()
}
sharedPrefs.dns_servers = emptyList<String>().toMutableList()
}
}
@ -193,15 +176,9 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
sharedPrefs = PreferenceHelper.defaultPreference(this)
clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
gson = GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create()
migrateServerList()
items = sharedPrefs.dns_servers
if (items[0] == "") {
items.removeAt(0)
}
adapter = ServerListRecyclerAdapter(true)
binding.recyclerView.adapter = adapter
@ -249,7 +226,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
R.id.export_settings_clipboard -> {
dnsServerViewModel.viewModelScope.launch {
val data = BackupUtils.export(dnsServerViewModel, sharedPrefs)
val jsonData = gson.toJson(data)
val jsonData = Json.encodeToString(data)
clipboard.setPrimaryClip(ClipData.newPlainText("", jsonData))
// Only show a toast for Android 12 and lower.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast.makeText(
@ -263,7 +240,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
val activityContext = this
dnsServerViewModel.viewModelScope.launch {
val data = BackupUtils.export(dnsServerViewModel, sharedPrefs)
val jsonData = gson.toJson(data)
val jsonData = Json.encodeToString(data)
ShareCompat.IntentBuilder(activityContext).setText(jsonData)
.setType("text/plain")
.startChooser()
@ -319,7 +296,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
data?.data?.also { uri ->
val jsonData = gson.toJson(BackupUtils.export(dnsServerViewModel, sharedPrefs))
val jsonData = Json.encodeToString(BackupUtils.export(dnsServerViewModel, sharedPrefs))
val contentResolver = applicationContext.contentResolver
runCatching {
contentResolver.openOutputStream(uri)?.use { outputStream ->
@ -378,7 +355,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
Shizuku.requestPermission(1)
}
} else {
grantPermissionWithShizuku()
grantPermission()
}
} else {
if (checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) {
@ -386,6 +363,9 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
Intent.ACTION_VIEW,
Uri.parse("https://karasevm.github.io/PrivateDNSAndroid/")
)
Toast.makeText(
this, R.string.shizuku_failure_toast, Toast.LENGTH_SHORT
).show()
startActivity(browserIntent)
finish()
}
@ -469,58 +449,27 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
dnsServerViewModel.update(id, server, label, null, enabled)
}
/**
* Attempts to grant WRITE_SECURE_SETTINGS permission with Shizuku
*/
private fun grantPermissionWithShizuku() {
val packageName = applicationContext.packageName
runCatching {
if (Build.VERSION.SDK_INT >= 31) {
HiddenApiBypass.addHiddenApiExemptions("Landroid/permission")
val binder =
ShizukuBinderWrapper(SystemServiceHelper.getSystemService("permissionmgr"))
val pm = IPermissionManager.Stub.asInterface(binder)
runCatching {
pm.grantRuntimePermission(
packageName, Manifest.permission.WRITE_SECURE_SETTINGS, 0
)
}.onFailure { _ ->
if (Build.VERSION.SDK_INT >= 34) {
pm.grantRuntimePermission(
packageName,
Manifest.permission.WRITE_SECURE_SETTINGS,
applicationContext.deviceId,
0
)
}
}
} else {
val binder = ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package"))
val pm = IPackageManager.Stub.asInterface(binder)
pm.grantRuntimePermission(
packageName, Manifest.permission.WRITE_SECURE_SETTINGS, 0
)
}
}.onFailure { e ->
Log.e("SHIZUKU", "onRequestPermissionResult: ", e)
}.also {
if (checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) {
val browserIntent = Intent(
Intent.ACTION_VIEW, Uri.parse("https://karasevm.github.io/PrivateDNSAndroid/")
)
startActivity(browserIntent)
finish()
}
private fun grantPermission() {
if (grantPermissionWithShizuku(this)) {
Toast.makeText(
this, R.string.shizuku_success_toast, Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
this, R.string.shizuku_failure_toast, Toast.LENGTH_SHORT
).show()
val browserIntent = Intent(
Intent.ACTION_VIEW, Uri.parse("https://karasevm.github.io/PrivateDNSAndroid/")
)
startActivity(browserIntent)
finish()
}
}
override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
val isGranted = grantResult == PackageManager.PERMISSION_GRANTED
if (isGranted) {
grantPermissionWithShizuku()
} else if (checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) {
if (!isGranted && checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) {
val browserIntent = Intent(
Intent.ACTION_VIEW, Uri.parse("https://karasevm.github.io/PrivateDNSAndroid/")
)

View file

@ -14,6 +14,7 @@ import ru.karasevm.privatednstoggle.util.PrivateDNSUtils
class OptionsDialogFragment : DialogFragment() {
private var _binding: DialogOptionsBinding? = null
private val binding get() = _binding!!
private val sharedPreferences by lazy { PreferenceHelper.defaultPreference(requireContext()) }
override fun onCreateDialog(
savedInstanceState: Bundle?
@ -34,8 +35,7 @@ class OptionsDialogFragment : DialogFragment() {
override fun onStart() {
super.onStart()
val sharedPrefs = PreferenceHelper.defaultPreference(requireContext())
val autoModeOption = sharedPrefs.autoMode
val autoModeOption = sharedPreferences.autoMode
when (autoModeOption) {
PrivateDNSUtils.AUTO_MODE_OPTION_OFF -> binding.autoOptionRadioGroup.check(R.id.autoOptionOff)
PrivateDNSUtils.AUTO_MODE_OPTION_AUTO -> binding.autoOptionRadioGroup.check(R.id.autoOptionAuto)
@ -44,20 +44,20 @@ class OptionsDialogFragment : DialogFragment() {
}
binding.autoOptionRadioGroup.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
R.id.autoOptionOff -> sharedPrefs.autoMode = PrivateDNSUtils.AUTO_MODE_OPTION_OFF
R.id.autoOptionAuto -> sharedPrefs.autoMode = PrivateDNSUtils.AUTO_MODE_OPTION_AUTO
R.id.autoOptionOffAuto -> sharedPrefs.autoMode =
R.id.autoOptionOff -> sharedPreferences.autoMode = PrivateDNSUtils.AUTO_MODE_OPTION_OFF
R.id.autoOptionAuto -> sharedPreferences.autoMode = PrivateDNSUtils.AUTO_MODE_OPTION_AUTO
R.id.autoOptionOffAuto -> sharedPreferences.autoMode =
PrivateDNSUtils.AUTO_MODE_OPTION_OFF_AUTO
R.id.autoOptionPrivate -> sharedPrefs.autoMode =
R.id.autoOptionPrivate -> sharedPreferences.autoMode =
PrivateDNSUtils.AUTO_MODE_OPTION_PRIVATE
}
}
val requireUnlock = sharedPrefs.requireUnlock
val requireUnlock = sharedPreferences.requireUnlock
binding.requireUnlockSwitch.isChecked = requireUnlock
binding.requireUnlockSwitch.setOnCheckedChangeListener { _, isChecked ->
sharedPrefs.requireUnlock = isChecked
sharedPreferences.requireUnlock = isChecked
}
}
}

View file

@ -1,20 +1,26 @@
package ru.karasevm.privatednstoggle.util
import android.content.SharedPreferences
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import ru.karasevm.privatednstoggle.data.DnsServerViewModel
import ru.karasevm.privatednstoggle.model.DnsServer
import ru.karasevm.privatednstoggle.util.PreferenceHelper.AUTO_MODE
import ru.karasevm.privatednstoggle.util.PreferenceHelper.DNS_SERVERS
import ru.karasevm.privatednstoggle.util.PreferenceHelper.REQUIRE_UNLOCK
import ru.karasevm.privatednstoggle.util.PreferenceHelper.autoMode
import ru.karasevm.privatednstoggle.util.PreferenceHelper.requireUnlock
object BackupUtils {
@Serializable
data class Backup(
@SerializedName("dns_servers") val dnsServers: List<DnsServer>,
@SerializedName("auto_mode") val autoMode: Int?,
@SerializedName("require_unlock") val requireUnlock: Boolean?,
@SerialName("dns_servers") val dnsServers: List<DnsServer>,
@SerialName("auto_mode") val autoMode: Int?,
@SerialName("require_unlock") val requireUnlock: Boolean?,
)
@Serializable
data class LegacyBackup(
@SerialName("dns_servers") val dnsServers: String,
@SerialName("auto_mode") val autoMode: Int?,
@SerialName("require_unlock") val requireUnlock: Boolean?,
)
/**
@ -47,17 +53,16 @@ object BackupUtils {
/**
* Imports old server list
* @param map Deserialized backup
* @param legacyBackup Deserialized backup
* @param viewModel View model
* @param sharedPreferences Shared preferences
*/
fun importLegacy(
map: Map<String, Any>,
legacyBackup: LegacyBackup,
viewModel: DnsServerViewModel,
sharedPreferences: SharedPreferences
) {
map[DNS_SERVERS]?.let { servers ->
if (servers is String) {
legacyBackup.dnsServers.let { servers ->
val serverList = servers.split(",")
serverList.forEach { server ->
val parts = server.split(" : ")
@ -67,9 +72,8 @@ object BackupUtils {
viewModel.insert(DnsServer(0, server, ""))
}
}
}
}
sharedPreferences.autoMode = map[AUTO_MODE] as? Int ?: 0
sharedPreferences.requireUnlock = map[REQUIRE_UNLOCK] as? Boolean ?: false
sharedPreferences.autoMode = legacyBackup.autoMode?: 0
sharedPreferences.requireUnlock = legacyBackup.requireUnlock == true
}
}

View file

@ -5,9 +5,7 @@ import android.content.ContentResolver
import android.content.Context
import android.content.pm.PackageManager
import android.provider.Settings
import android.widget.Toast
import androidx.core.content.ContextCompat.checkSelfPermission
import ru.karasevm.privatednstoggle.R
@Suppress("unused")
object PrivateDNSUtils {
@ -45,15 +43,10 @@ object PrivateDNSUtils {
}
fun checkForPermission(context: Context): Boolean {
if (checkSelfPermission(
context,
Manifest.permission.WRITE_SECURE_SETTINGS
) == PackageManager.PERMISSION_GRANTED
) {
return true
}
Toast.makeText(context, R.string.permission_missing, Toast.LENGTH_SHORT).show()
return false
return checkSelfPermission(
context,
Manifest.permission.WRITE_SECURE_SETTINGS
) == PackageManager.PERMISSION_GRANTED
}
}

View file

@ -0,0 +1,82 @@
package ru.karasevm.privatednstoggle.util
import android.Manifest
import android.content.Context
import android.content.pm.IPackageManager
import android.os.Build
import android.os.Process
import android.os.UserHandle
import android.permission.IPermissionManager
import android.util.Log
import org.lsposed.hiddenapibypass.HiddenApiBypass
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper
import ru.karasevm.privatednstoggle.util.PrivateDNSUtils.checkForPermission
object ShizukuUtil {
private const val TAG = "ShizukuUtil"
/**
* Attempts to grant the WRITE_SECURE_SETTINGS permission using Shizuku.
*
* @param context The context from which the method is called.
* @return True if the permission was granted successfully, false otherwise.
*/
fun grantPermissionWithShizuku(context: Context): Boolean {
val packageName = context.packageName
var userId = 0
runCatching {
val userHandle = Process.myUserHandle()
userId = UserHandle::class.java.getMethod("getIdentifier").invoke(userHandle) as? Int ?: 0
}
if (Build.VERSION.SDK_INT >= 31) {
HiddenApiBypass.addHiddenApiExemptions("Landroid/permission")
val binder =
ShizukuBinderWrapper(SystemServiceHelper.getSystemService("permissionmgr"))
val pm = IPermissionManager.Stub.asInterface(binder)
runCatching {
pm.grantRuntimePermission(
packageName,
Manifest.permission.WRITE_SECURE_SETTINGS,
userId
)
}.onFailure { e ->
Log.w(TAG, "Android 12 method failed: ", e)
runCatching {
pm.grantRuntimePermission(
packageName,
Manifest.permission.WRITE_SECURE_SETTINGS,
0,
userId
)
}.onFailure { e ->
Log.w(TAG, "Android 14 QPR2 method failed: ", e)
runCatching {
pm.grantRuntimePermission(
packageName,
Manifest.permission.WRITE_SECURE_SETTINGS,
"default:0",
userId
)
}.onFailure { e ->
Log.w(TAG, "Android 14 QPR3 method failed: ", e)
}
}
}
} else {
val binder = ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package"))
val pm = IPackageManager.Stub.asInterface(binder)
runCatching {
pm.grantRuntimePermission(
packageName,
Manifest.permission.WRITE_SECURE_SETTINGS,
userId
)
}.onFailure { e ->
Log.w(TAG, "Android <12 method failed: ", e)
}
}
return checkForPermission(context)
}
}

View file

@ -4,7 +4,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
tools:context=".ui.MainActivity"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/topAppBarLayout"

View file

@ -0,0 +1 @@
unqualifiedResLocale=en-US

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="done">Terminé</string>
<string name="app_name">Private DNS Quick Toggle</string>
<string name="dns_unknown">Inconnu</string>
<string name="dns_off">Éteint</string>
<string name="add_server">Ajouter un serveur</string>
<string name="menu_add">Ajouter</string>
<string name="menu_privacy_policy">Politique de confidentialité</string>
<string name="delete_message">Êtes-vous sûr de vouloir supprimer le serveur?</string>
<string name="delete">Supprimer</string>
<string name="server_length_error">L\'adresse du serveur ne peut pas être vide</string>
<string name="add_edittext_label_hint">Étiquette du serveur DNS (Facultatif)</string>
<string name="add_edittext_hint">Adresse du serveur DNS</string>
<string name="options">Options</string>
<string name="ok">OK</string>
<string name="auto_option_only_off">Seulement éteint</string>
<string name="auto_option_only_auto">Seulement automatique</string>
<string name="auto_option_off_and_auto">Éteint et automatique</string>
<string name="dns_auto">Automatique</string>
<string name="tile_name">Commutateur de DNS privé</string>
<string name="menu_save">Enregistrer</string>
<string name="permission_missing">Autorisation non accordée, vérifiez les instructions dans l\'application</string>
<string name="select_server">Sélectionner le serveur</string>
<string name="auto_option_description">Définissez les options à inclure</string>
<string name="auto_option_only_private">DNS privé seulement</string>
<string name="open_app">Ouvrir l\'application</string>
<string name="set_to_off_toast">DNS privé éteint</string>
<string name="set_to_auto_toast">DNS privé réglé sur automatique</string>
<string name="set_to_provider_toast">DNS privé réglé sur %1$s</string>
<string name="require_unlock_setting">Requiert le déverrouillage de l\'appareil pour changer de serveur</string>
<string name="add_server_enabled">Activé</string>
<string name="menu_import_from_file">À partir du fichier</string>
<string name="no_servers_added">Aucun serveur ajouté</string>
<string name="copy_success">Copié</string>
<string name="edit_server">Éditer le serveur</string>
<string name="export_failure">Échec de la sauvegarde</string>
<string name="empty_hint">Appuyez sur le bouton ci-dessous pour en ajouter un</string>
<string name="shizuku_failure_toast">Impossible d\'obtenir l\'autorisation, veuillez l\'accorder manuellement</string>
<string name="shizuku_success_toast">Autorisation accordée, vous pouvez désormais révoquer l\'autorisation Shizuku</string>
<string name="menu_import_from_clipboard">À partir du presse-papier</string>
<string name="menu_export_to_clipboard">Vers le presse-papier</string>
<string name="menu_export_share">Partager</string>
<string name="menu_export_to_file">Vers le fichier</string>
<string name="export_success">Sauvegarde réussie</string>
<string name="import_failure">Échec de l\'importation</string>
<string name="import_failure_json">Échec de l\'importation, le fichier JSON est incorrect</string>
<string name="import_success">Importé</string>
<string name="menu_export">Exporter</string>
<string name="menu_import">Importer</string>
<string name="a11y_drag_handle">Poignée</string>
<string name="delete_question">Supprimer</string>
<string name="cancel">Annuler</string>
</resources>

View file

@ -0,0 +1,51 @@
<resources>
<string name="app_name">Privát DNS Gyorskapcsoló</string>
<string name="tile_name">Privát DNS Kapcsoló</string>
<string name="permission_missing">Nincs engedély megadva, nézd meg az alkalmazásban, hogyan adhatod meg</string>
<string name="dns_off">Ki</string>
<string name="dns_auto">Automatikus</string>
<string name="dns_unknown">Ismeretlen</string>
<string name="add_server">Szerver hozzáadása</string>
<string name="menu_add">Hozzáadás</string>
<string name="menu_save">Mentés</string>
<string name="menu_privacy_policy">Adatvédelmi irányelvek</string>
<string name="select_server">Szerver kiválasztása</string>
<string name="done">Kész</string>
<string name="cancel">Mégse</string>
<string name="delete_question">Törlés</string>
<string name="delete_message">Biztosan törölni szeretnéd a szervert?</string>
<string name="delete">Törlés</string>
<string name="server_length_error">A szervercím nem lehet üres</string>
<string name="add_edittext_label_hint">DNS szerver neve (opcionális)</string>
<string name="add_edittext_hint">DNS szerver címe</string>
<string name="options">Beállítások</string>
<string name="ok">OK</string>
<string name="auto_option_description">Válaszd ki, mely opciók jelenjenek meg a csempén</string>
<string name="auto_option_only_off">Csak ki</string>
<string name="auto_option_only_auto">Csak automatikus</string>
<string name="auto_option_off_and_auto">Ki és automatikus</string>
<string name="auto_option_only_private">Csak Privát DNS</string>
<string name="open_app">Alkalmazás megnyitása</string>
<string name="set_to_off_toast">Privát DNS kikapcsolva</string>
<string name="set_to_auto_toast">Privát DNS automatikus módra állítva</string>
<string name="set_to_provider_toast">Privát DNS beállítva: %1$s</string>
<string name="require_unlock_setting">Eszköz feloldása szükséges a szerver módosításához</string>
<string name="a11y_drag_handle">Húzási fogantyú</string>
<string name="menu_import">Importálás</string>
<string name="menu_export">Exportálás</string>
<string name="import_success">Importálva</string>
<string name="import_failure">Importálás sikertelen</string>
<string name="import_failure_json">Importálás sikertelen, hibás JSON</string>
<string name="copy_success">Másolva</string>
<string name="menu_import_from_file">Fájlból</string>
<string name="menu_import_from_clipboard">Vágólapról</string>
<string name="menu_export_to_clipboard">Vágólapra</string>
<string name="menu_export_share">Megosztás</string>
<string name="menu_export_to_file">Fájlba</string>
<string name="export_failure">Mentés sikertelen</string>
<string name="export_success">Sikeresen mentve</string>
<string name="edit_server">Szerver szerkesztése</string>
<string name="no_servers_added">Nincsenek szerverek hozzáadva</string>
<string name="empty_hint">Koppints az alábbi gombra, hogy hozzáadj egyet</string>
<string name="add_server_enabled">Engedélyezve</string>
</resources>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="tile_name">Хувийн DNS солих</string>
<string name="dns_off">Унтраах</string>
<string name="dns_unknown">Тодорхойгүй</string>
<string name="add_server">Сервер нэмэх</string>
<string name="menu_save">Хадгалах</string>
<string name="done">Болсон</string>
<string name="cancel">Болих</string>
<string name="delete_question">Устгах</string>
<string name="delete_message">Та серверийг устгахдаа итгэлтэй байна уу?</string>
<string name="delete">Устгах</string>
<string name="server_length_error">Серверийн хаяг хоосон байж болохгүй</string>
<string name="add_edittext_label_hint">DNS серверийн шошго (заавал биш)</string>
<string name="add_edittext_hint">DNS серверийн хаяг</string>
<string name="options">Сонголтууд</string>
<string name="ok">ОК</string>
<string name="auto_option_description">Хавтан дээр ямар сонголтыг оруулахаа сонгоно уу</string>
<string name="auto_option_only_off">Зөвхөн унтарсан</string>
<string name="auto_option_only_auto">Зөвхөн авто</string>
<string name="auto_option_off_and_auto">Унтарсан болон авто</string>
<string name="set_to_provider_toast">Хувийн DNS-г %1$s болгож тохируулсан</string>
<string name="require_unlock_setting">Серверийг өөрчлөхийн тулд төхөөрөмжийн түгжээг тайлах шаардлагатай</string>
<string name="a11y_drag_handle">Бариулыг чирэх</string>
<string name="menu_import">Импорт</string>
<string name="import_success">Импортолсон</string>
<string name="import_failure">Импорт хийж чадсангүй</string>
<string name="menu_import_from_file">Файлаас</string>
<string name="export_failure">Хадгалж чадсангүй</string>
<string name="export_success">Амжилттай хадгалсан</string>
<string name="edit_server">Сервер засах</string>
<string name="app_name">Хувийн DNS хурдан сэлгэх</string>
<string name="menu_privacy_policy">Нууцлалын бодлого</string>
<string name="permission_missing">Зөвшөөрөл олгоогүй. Үүнийг хэрхэн хийхийг харна уу</string>
<string name="dns_auto">Авто</string>
<string name="menu_add">Нэмэх</string>
<string name="select_server">Сервер сонгох</string>
<string name="auto_option_only_private">Зөвхөн хувийн DNS</string>
<string name="open_app">Апп нээх</string>
<string name="menu_export_to_clipboard">Түр санах ой руу</string>
<string name="set_to_off_toast">Хувийн DNS унтарсан</string>
<string name="set_to_auto_toast">Хувийн DNS-г автоматаар тохируулсан</string>
<string name="menu_export">Экспорт</string>
<string name="copy_success">Хуулагдсан</string>
<string name="menu_export_share">Хуваалцах</string>
<string name="import_failure_json">Импорт хийж чадсангүй, алдаатай JSON</string>
<string name="menu_import_from_clipboard">Түр санах ойноос</string>
<string name="menu_export_to_file">Файлруу</string>
</resources>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="delete">Usuń</string>
<string name="menu_save">Zapisz</string>
<string name="menu_privacy_policy">Polityka prywatności</string>
<string name="select_server">Wybierz serwer</string>
<string name="cancel">Anuluj</string>
<string name="add_edittext_hint">Adres serwera DNS</string>
<string name="ok">OK</string>
<string name="open_app">Otwórz aplikację</string>
<string name="menu_import">Importuj</string>
<string name="menu_export">Eksportuj</string>
<string name="copy_success">Skopiowano</string>
<string name="menu_import_from_file">Z pliku</string>
<string name="menu_import_from_clipboard">Ze schowka</string>
<string name="menu_export_to_clipboard">Do schowka</string>
<string name="menu_export_share">Udostępnij</string>
<string name="menu_export_to_file">Do pliku</string>
<string name="edit_server">Edytuj serwer</string>
<string name="no_servers_added">Brak dodanych serwerów</string>
<string name="add_server_enabled">Włączone</string>
<string name="add_server">Dodaj serwer</string>
<string name="menu_add">Dodaj</string>
<string name="delete_question">Usuń</string>
<string name="dns_unknown">Nieznane</string>
<string name="done">Gotowe</string>
<string name="options">Opcje</string>
<string name="import_failure">Importowanie nie powiodło się</string>
<string name="dns_auto">Automatycznie</string>
<string name="dns_off">Wyłącz</string>
<string name="import_success">Zaimportowano</string>
<string name="server_length_error">Adres serwera nie może być pusty</string>
<string name="import_failure_json">Import nie powiódł się, zniekształcony plik JSON</string>
<string name="export_success">Zapisano pomyślnie</string>
<string name="delete_message">Czy na pewno chcesz usunąć serwer?</string>
<string name="app_name">Private DNS Quick Toggle</string>
<string name="tile_name">Przełącznik prywatnego DNS</string>
<string name="permission_missing">Nieprzydzielono uprawnienia, sprawdź w aplikacji, w jaki sposób można to zrobić</string>
<string name="add_edittext_label_hint">Opis serwera DNS (opcjonalnie)</string>
<string name="auto_option_description">Wybierz opcje, które będą dostępne w kafelku</string>
<string name="auto_option_only_off">Tylko wyłączenie</string>
<string name="auto_option_only_auto">Tylko automatycznie</string>
<string name="auto_option_off_and_auto">Wyłączenie i automatycznie</string>
<string name="auto_option_only_private">Tylko prywatny DNS</string>
<string name="set_to_auto_toast">Prywatny DNS zmieniony na automatyczny</string>
<string name="set_to_provider_toast">Prywatny DNS zmieniony na %1$s</string>
<string name="require_unlock_setting">Wymagaj odblokowania urządzenia do zmiany serwera</string>
<string name="set_to_off_toast">Wyłączono Prywatny DNS</string>
<string name="a11y_drag_handle">Przeciągnij</string>
<string name="export_failure">Zapisywanie nie powiodło się</string>
<string name="empty_hint">Kliknij na poniższy przycisk, aby dodać nowy</string>
<string name="shizuku_success_toast">Udzielono zezwolenia, możesz teraz cofnąć zezwolenie w Shizuku</string>
<string name="shizuku_failure_toast">Uzyskanie uprawnień nie powiodło się, udziel ich ręcznie</string>
</resources>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="import_success">Importado</string>
<string name="permission_missing">Permissão não concedida, verifique o app para saber como prosseguir</string>
<string name="app_name">Alteração de DNS privado</string>
<string name="cancel">Cancelar</string>
<string name="add_server">Adicionar servidor</string>
<string name="add_edittext_hint">Endereço do servidor DNS</string>
<string name="auto_option_description">Escolha opções disponível em atalho</string>
<string name="tile_name">Alteração de DNS privado</string>
<string name="dns_off">Desativado</string>
<string name="dns_auto">Automático</string>
<string name="dns_unknown">Indeterminado</string>
<string name="menu_add">Adicionar</string>
<string name="menu_save">Salvar</string>
<string name="menu_privacy_policy">Política de privacidade</string>
<string name="done">Concluído</string>
<string name="delete_question">Apagar</string>
<string name="delete_message">Tem certeza de que quer apagar o servidor?</string>
<string name="delete">Apagar</string>
<string name="server_length_error">O endereço do servidor não pode estar em branco</string>
<string name="add_edittext_label_hint">Identificação do servidor DNS (opcional)</string>
<string name="options">Opções</string>
<string name="ok">Ok</string>
<string name="auto_option_only_off">Somente desativado</string>
<string name="auto_option_off_and_auto">Desativado e automático</string>
<string name="auto_option_only_private">Somente DNS privado</string>
<string name="open_app">Abrir app</string>
<string name="set_to_off_toast">DNS privado desativado</string>
<string name="set_to_auto_toast">DNS privado definido para automático</string>
<string name="set_to_provider_toast">DNS privado definido para %1$s</string>
<string name="a11y_drag_handle">Arrastre</string>
<string name="menu_import">Importar</string>
<string name="menu_export">Exportar</string>
<string name="import_failure_json">Falha na importação, JSON malformado</string>
<string name="copy_success">Copiado</string>
<string name="menu_import_from_clipboard">Da memória</string>
<string name="menu_export_share">Compartilhar</string>
<string name="menu_export_to_file">Para arquivo</string>
<string name="export_success">Salvo com sucesso</string>
<string name="edit_server">Editar servidor</string>
<string name="no_servers_added">Nenhum servidor adicionado</string>
<string name="empty_hint">Toque no botão abaixo para adicionar</string>
<string name="add_server_enabled">Ativado</string>
<string name="select_server">Escolha servidor</string>
<string name="menu_export_to_clipboard">Para memória</string>
<string name="menu_import_from_file">De arquivo</string>
<string name="import_failure">Falha ao importar</string>
<string name="export_failure">Falha ao salvar</string>
<string name="require_unlock_setting">Necessário desbloquear o dispositivo para alterar servidor</string>
<string name="auto_option_only_auto">Somente automático</string>
<string name="shizuku_failure_toast">Falha ao obter a permissão. Tente conceder manualmente</string>
<string name="shizuku_success_toast">Permissão concedida, você pode revogar a permissão do Shizuku agora</string>
</resources>

View file

@ -0,0 +1,53 @@
<resources>
<string name="app_name">Private DNS Quick Toggle</string>
<string name="tile_name">Переключить частный DNS</string>
<string name="permission_missing">Разрешение не предоставлено, проверьте приложение для получения информации</string>
<string name="dns_off">Выкл</string>
<string name="dns_auto">Авто</string>
<string name="dns_unknown">Неизвестно</string>
<string name="add_server">Добавить сервер</string>
<string name="menu_add">Добавить</string>
<string name="menu_save">Сохранить</string>
<string name="menu_privacy_policy">Политика конфиденциальности</string>
<string name="select_server">Выбрать сервер</string>
<string name="done">Готово</string>
<string name="cancel">Отмена</string>
<string name="delete_question">Удалить</string>
<string name="delete_message">Вы уверены, что хотите удалить сервер?</string>
<string name="delete">Удалить</string>
<string name="server_length_error">Адрес сервера не может быть пустым</string>
<string name="add_edittext_label_hint">Название DNS сервера (необязательно)</string>
<string name="add_edittext_hint">Адрес DNS сервера</string>
<string name="options">Опции</string>
<string name="ok">OK</string>
<string name="auto_option_description">Выберите, какие опции включить в плитке</string>
<string name="auto_option_only_off">Только \"Выкл\"</string>
<string name="auto_option_only_auto">Только \"Авто\"</string>
<string name="auto_option_off_and_auto">\"Выкл\" и \"Авто\"</string>
<string name="auto_option_only_private">Только частный DNS</string>
<string name="open_app">Открыть приложение</string>
<string name="set_to_off_toast">Частный DNS выключен</string>
<string name="set_to_auto_toast">Частный DNS установлен на "Авто"</string>
<string name="set_to_provider_toast">Частный DNS установлен на %1$s</string>
<string name="require_unlock_setting">Смена сервера требует разблокировки устройства</string>
<string name="a11y_drag_handle">Ручка перетаскивания</string>
<string name="menu_import">Импорт</string>
<string name="menu_export">Экспорт</string>
<string name="import_success">Успешно импортировано</string>
<string name="import_failure">Импорт не удался</string>
<string name="import_failure_json">Импорт не удался, некорректный JSON</string>
<string name="copy_success">Скопировано</string>
<string name="menu_import_from_file">Из файла</string>
<string name="menu_import_from_clipboard">Из буфера обмена</string>
<string name="menu_export_to_clipboard">В буфер обмена</string>
<string name="menu_export_share">Поделиться</string>
<string name="menu_export_to_file">В файл</string>
<string name="export_failure">Сохранение не удалось</string>
<string name="export_success">Успешно сохранено</string>
<string name="edit_server">Редактировать сервер</string>
<string name="no_servers_added">Нет доступных серверов</string>
<string name="empty_hint">Нажмите на кнопку ниже, чтобы добавить сервер</string>
<string name="add_server_enabled">Включён</string>
<string name="shizuku_success_toast">Разрешение получено, можно отозвать авторизацию Shizuku</string>
<string name="shizuku_failure_toast">Не удалось получить разрешение, предоставьте его вручную</string>
</resources>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ok">சரி</string>
<string name="app_name">தனியார் டி.என்.எச் விரைவாக மாற்று</string>
<string name="tile_name">தனியார் டி.என் கள் மாறுகின்றன</string>
<string name="permission_missing">இசைவு வழங்கப்படவில்லை, அதை எப்படி செய்வது என்று பார்க்க பயன்பாட்டை சரிபார்க்கவும்</string>
<string name="dns_off">அணை</string>
<string name="dns_auto">தானி</string>
<string name="dns_unknown">தெரியவில்லை</string>
<string name="add_server">சேவையகத்தைச் சேர்க்கவும்</string>
<string name="menu_add">கூட்டு</string>
<string name="menu_save">சேமி</string>
<string name="menu_privacy_policy">தனியுரிமைக் கொள்கை</string>
<string name="select_server">சேவையகத்தைத் தேர்ந்தெடுக்கவும்</string>
<string name="done">முடிந்தது</string>
<string name="cancel">ரத்துசெய்</string>
<string name="delete_question">நீக்கு</string>
<string name="delete_message">சேவையகத்தை நீக்க விரும்புகிறீர்களா?</string>
<string name="delete">நீக்கு</string>
<string name="add_edittext_hint">டிஎன்எச் சேவையக முகவரி</string>
<string name="options">விருப்பங்கள்</string>
<string name="auto_option_description">ஓடுகளில் எந்த விருப்பங்களைச் சேர்க்க வேண்டும் என்பதைத் தேர்வுசெய்க</string>
<string name="auto_option_only_off">மட்டுமே</string>
<string name="auto_option_only_auto">ஆட்டோ மட்டுமே</string>
<string name="auto_option_off_and_auto">ஆஃப் மற்றும் ஆட்டோ</string>
<string name="auto_option_only_private">தனியார் டி.என்.எச் மட்டுமே</string>
<string name="open_app">திறந்த பயன்பாடு</string>
<string name="set_to_off_toast">தனியார் டி.என்.எச் அணைக்கப்பட்டது</string>
<string name="set_to_auto_toast">தனியார் டி.என்.எச் ஆட்டோவாக அமைக்கப்பட்டுள்ளது</string>
<string name="set_to_provider_toast">தனியார் டி.என்.எச் %1$s என அமைக்கப்பட்டுள்ளது</string>
<string name="require_unlock_setting">சேவையகத்தை மாற்ற சாதனத்தைத் திறக்க வேண்டும்</string>
<string name="a11y_drag_handle">இழுவை கைப்பிடி</string>
<string name="menu_import">இறக்குமதி</string>
<string name="menu_export">ஏற்றுமதி</string>
<string name="import_success">இறக்குமதி செய்யப்பட்டது</string>
<string name="import_failure">இறக்குமதி தோல்வியடைந்தது</string>
<string name="import_failure_json">இறக்குமதி தோல்வியுற்றது, தவறாக சாதொபொகு</string>
<string name="copy_success">நகலெடுக்கப்பட்டது</string>
<string name="menu_import_from_file">கோப்பிலிருந்து</string>
<string name="menu_import_from_clipboard">கிளிப்போர்டிலிருந்து</string>
<string name="menu_export_to_clipboard">இடைநிலைப்பலகைக்கு</string>
<string name="menu_export_share">பங்கு</string>
<string name="menu_export_to_file">தாக்கல் செய்ய</string>
<string name="export_failure">சேமிப்பு தோல்வியடைந்தது</string>
<string name="export_success">வெற்றிகரமாக சேமிக்கப்பட்டது</string>
<string name="edit_server">சேவையகத்தைத் திருத்து</string>
<string name="no_servers_added">சேவையகங்கள் எதுவும் சேர்க்கப்படவில்லை</string>
<string name="empty_hint">ஒன்றைச் சேர்க்க கீழே உள்ள பொத்தானைத் தட்டவும்</string>
<string name="add_server_enabled">இயக்கப்பட்டது</string>
<string name="shizuku_success_toast">இசைவு வழங்கப்பட்டது, நீங்கள் இப்போது சிசுகு அனுமதியை ரத்து செய்யலாம்</string>
<string name="shizuku_failure_toast">இசைவு பெறுவதில் தோல்வி, தயவுசெய்து அதை கைமுறையாக வழங்கவும்</string>
<string name="server_length_error">சேவையக முகவரி காலியாக இருக்க முடியாது</string>
<string name="add_edittext_label_hint">டிஎன்எச் சேவையக சிட்டை (விரும்பினால்)</string>
</resources>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="delete_message">Sunucuyu silmek istediğinizden emin misiniz?</string>
<string name="auto_option_only_off">Sadece kapalı</string>
<string name="require_unlock_setting">Sunucuyu değiştirmek için cihazın kilidini açmanız gerekiyor</string>
<string name="dns_auto">Oto</string>
<string name="dns_unknown">Bilinmeyen</string>
<string name="done">Tamam</string>
<string name="cancel">İptal</string>
<string name="add_edittext_label_hint">DNS sunucusu Etiketi (İsteğe bağlı)</string>
<string name="options">Seçenekler</string>
<string name="ok">OK</string>
<string name="auto_option_only_auto">Sadece oto</string>
<string name="auto_option_off_and_auto">Kapalı ve oto</string>
<string name="set_to_off_toast">Özel DNS kapandı</string>
<string name="open_app">Uygulamayıınız</string>
<string name="menu_import">İçe aktar</string>
<string name="menu_export">Dışa aktar</string>
<string name="import_success">içe aktarıldı</string>
<string name="menu_export_to_file">Dosyaya</string>
<string name="export_failure">Kaydetme başarısız oldu</string>
<string name="no_servers_added">Hiç Sunucu Eklenmedi</string>
<string name="empty_hint">Eklemek için aşağıdaki düğmeye dokunun</string>
<string name="shizuku_failure_toast">İzin alınamadı, lütfen manuel olarak verin</string>
<string name="auto_option_description">Karoya hangi seçeneklerin dahil edileceğini seçiniz</string>
<string name="dns_off">Kapalı</string>
<string name="menu_privacy_policy">Gizlilik Politikası</string>
<string name="delete_question">Sil</string>
<string name="server_length_error">Sunucu adresi boş olamaz</string>
<string name="delete">Sil</string>
<string name="add_edittext_hint">DNS sunucu adresi</string>
<string name="a11y_drag_handle">tutacağı sürükle</string>
<string name="menu_add">Ekle</string>
<string name="add_server">Sunucu Ekleyiniz</string>
<string name="permission_missing">İzin verilmedi, nasıl yapıldığını görmek için uygulamayı kontrol ediniz</string>
<string name="import_failure_json">İçe aktarma başarısız oldu, hatalı biçimlendirilmiş JSON</string>
<string name="app_name">Özel DNS Hızlı Geçiş</string>
<string name="shizuku_success_toast">İzin verildi, şimdi Shizuku iznini iptal edebilirsiniz</string>
<string name="import_failure">Aktarma başarısız</string>
<string name="menu_import_from_clipboard">Panodan</string>
<string name="export_success">Başarıyla kaydedildi</string>
<string name="menu_save">Kaydet</string>
<string name="set_to_auto_toast">Özel DNS otomatik olarak ayarlandı</string>
<string name="set_to_provider_toast">Özel DNS %1$s olarak ayarlandı</string>
<string name="auto_option_only_private">Sadece Özel DNS</string>
<string name="select_server">Sunucuyu Seçin</string>
<string name="copy_success">Kopyalandı</string>
<string name="edit_server">Sunucuyu düzenle</string>
<string name="add_server_enabled">Etkin</string>
<string name="tile_name">Özel DNS Geçişi</string>
<string name="menu_import_from_file">Dosyadan</string>
<string name="menu_export_to_clipboard">Panoya</string>
<string name="menu_export_share">Paylaş</string>
</resources>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="add_server">Thêm máy chủ</string>
<string name="auto_option_only_auto">Chỉ tự động</string>
<string name="permission_missing">Chưa được cấp quyền, hãy kiểm tra ứng dụng để biết cách thực hiện</string>
<string name="dns_off">Tắt</string>
<string name="dns_auto">Tự động</string>
<string name="menu_add">Thêm</string>
<string name="menu_privacy_policy">Chính sách bảo mật</string>
<string name="select_server">Chọn máy chủ</string>
<string name="done">Hoàn thành</string>
<string name="cancel">Hủy</string>
<string name="delete_message">Bạn có chắc chắn muốn xóa máy chủ không?</string>
<string name="delete">Xoá</string>
<string name="add_edittext_hint">Địa chỉ máy chủ DNS</string>
<string name="options">Tùy chọn</string>
<string name="ok">OK</string>
<string name="auto_option_description">Chọn các tùy chọn để đưa vào ô</string>
<string name="auto_option_only_off">Chỉ tắt</string>
<string name="auto_option_off_and_auto">Tắt và tự động</string>
<string name="open_app">Mở ứng dụng</string>
<string name="set_to_auto_toast">DNS cá nhân được thiết lập tự động</string>
<string name="require_unlock_setting">Yêu cầu mở khóa thiết bị để thay đổi máy chủ</string>
<string name="a11y_drag_handle">Tay cầm kéo</string>
<string name="menu_export">Xuất</string>
<string name="import_success">Đã nhập</string>
<string name="import_failure">Nhập thất bại</string>
<string name="import_failure_json">Nhập thất bại, JSON bị lỗi</string>
<string name="copy_success">Đã sao chép</string>
<string name="menu_import_from_file">Từ tập tin</string>
<string name="menu_export_share">Chia sẻ</string>
<string name="menu_export_to_file">Thành tập tin</string>
<string name="export_failure">Lưu không thành công</string>
<string name="edit_server">Chỉnh sửa máy chủ</string>
<string name="no_servers_added">Chưa có máy chủ nào</string>
<string name="empty_hint">Nhấn vào nút bên dưới để thêm</string>
<string name="add_server_enabled">Đã bật</string>
<string name="tile_name">Chuyển đổi DNS cá nhân</string>
<string name="delete_question">Xoá</string>
<string name="app_name">Chuyển đổi nhanh DNS cá nhân</string>
<string name="dns_unknown">Không rõ</string>
<string name="add_edittext_label_hint">Nhãn máy chủ DNS (Không bắt buộc)</string>
<string name="menu_save">Lưu</string>
<string name="server_length_error">Địa chỉ máy chủ không được để trống</string>
<string name="auto_option_only_private">Chỉ DNS cá nhân</string>
<string name="set_to_off_toast">Đã tắt DNS cá nhân</string>
<string name="set_to_provider_toast">DNS cá nhân được đặt thành %1$s</string>
<string name="menu_import">Nhập</string>
<string name="menu_import_from_clipboard">Từ bảng nhớ tạm</string>
<string name="menu_export_to_clipboard">Vào bảng nhớ tạm</string>
<string name="export_success">Đã lưu thành công</string>
</resources>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="tile_name">私有DNS触发</string>
<string name="permission_missing">必要权限未授予,请看相关说明</string>
<string name="dns_off">关闭</string>
<string name="dns_auto">自动</string>
<string name="dns_unknown">未知</string>
<string name="add_server">添加DNS服务器</string>
<string name="menu_add">添加</string>
<string name="menu_save">存储</string>
<string name="menu_privacy_policy">隐私策略</string>
<string name="select_server">选择服务器</string>
<string name="done">完成</string>
<string name="cancel">取消</string>
<string name="delete_question">删除条目</string>
<string name="delete_message">你确认要删除这个服务器条目吗?</string>
<string name="delete">删除</string>
<string name="server_length_error">服务器地址不可为空</string>
<string name="add_edittext_label_hint">DNS服务器标识</string>
<string name="add_edittext_hint">DNS服务器地址</string>
<string name="options">选项</string>
<string name="ok">确认</string>
<string name="auto_option_description">选择要在磁贴中启用的选项</string>
<string name="auto_option_only_off">仅“关闭”</string>
<string name="auto_option_only_auto">仅“自动”</string>
<string name="auto_option_off_and_auto">“关闭“与”自动“</string>
<string name="auto_option_only_private">仅设置的私有DNS</string>
<string name="open_app">打开软件</string>
<string name="set_to_off_toast">不使用私有DNS</string>
<string name="set_to_auto_toast">自动使用私有DNS</string>
<string name="set_to_provider_toast">设置为使用私有DNS\"%1$s\"</string>
<string name="require_unlock_setting">更改服务器设置要求设备解锁</string>
<string name="a11y_drag_handle">拖动把手</string>
<string name="menu_import">导入</string>
<string name="menu_export">导出</string>
<string name="import_success">已导入</string>
<string name="import_failure">导入失败</string>
<string name="import_failure_json">导入失败json格式异常</string>
<string name="copy_success">已复制</string>
<string name="menu_import_from_file">从文件导入</string>
<string name="menu_import_from_clipboard">从剪贴板导入</string>
<string name="menu_export_to_clipboard">导出至剪贴板</string>
<string name="menu_export_share">分享</string>
<string name="menu_export_to_file">导出至文件</string>
<string name="export_failure">保存失败</string>
<string name="export_success">保存成功</string>
<string name="edit_server">编辑服务器条目</string>
<string name="no_servers_added">无可用服务器</string>
<string name="empty_hint">点击下方\"+\"添加一个吧</string>
<string name="add_server_enabled">已启用</string>
</resources>

View file

@ -48,4 +48,6 @@
<string name="no_servers_added">No Servers Added</string>
<string name="empty_hint">Tap on the button below to add one</string>
<string name="add_server_enabled">Enabled</string>
<string name="shizuku_success_toast">Permission granted, you can revoke the Shizuku permission now</string>
<string name="shizuku_failure_toast">Failed to acquire permission, please grant it manually</string>
</resources>

View file

@ -5,18 +5,15 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.5.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10"
classpath("com.android.tools.build:gradle:8.8.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
plugins {
id 'org.jetbrains.kotlin.android' version '1.9.10' apply false
id "com.google.devtools.ksp" version "1.9.10-1.0.13" apply false
id("org.jetbrains.kotlin.android") version "2.0.20" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20"
id("com.google.devtools.ksp") version "2.0.20-1.0.25" apply false
}
tasks.register('clean', Delete) {
delete rootProject.layout.buildDirectory
}

View file

@ -0,0 +1,11 @@
- Replaced server storage backend with Room, allowing for easier further expansion
- Add option to disable saved servers
- Improved backup handling
- Fixed desync bug while dragging servers
- Reorganized source file structure
- Updated Kotlin version
- Updated Java version
- Replaced gson with kotlinx.serialization
- Add Chinese Simplified translation (thanks @WeiguangTWK)
- Add Russian translation
- Fixed issue with provider not resetting when disabled through the dialog

View file

@ -0,0 +1,12 @@
- Add Shizuku support for newer Android versions
- Fix some crashes
- Improve Shizuku process feedback
- Fix Shizuku when not running as the primary user
- Hungarian translation by @Pacuka in https://github.com/karasevm/PrivateDNSAndroid/pull/43
- Add Polish translation (Michal L (@chuckmichael), Eryk Michalak (gnu-ewm))
- Add Mongolian translation (Purevbaatar Tuvshinjargal (@puujee0238))
- Add Portuguese (Brazil) translation (ajan, Víctor Assunção (@JoaoVictorAS))
- Add Vietnamese translation (tuấn nguyễn (@Tuan1-2-3))
- Add French translation (papaindiatango)
- Add Tamil translation (தமிழ்நேரம் (@TamilNeram))
- Add Turkish translation (Mustafa A. (mistiik99))

View file

@ -1,6 +1,6 @@
#Mon Aug 16 15:36:35 MSK 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View file

@ -6,4 +6,4 @@ dependencyResolutionManagement {
}
}
rootProject.name = "Private DNS Quick Toggle"
include ':app'
include("app")