diff --git a/.github/ISSUE_TEMPLATE/01-bug-report.yml b/.github/ISSUE_TEMPLATE/01-bug-report.yml deleted file mode 100644 index c906e0f..0000000 --- a/.github/ISSUE_TEMPLATE/01-bug-report.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Bug report -description: File a bug report. -labels: ['bug'] -assignees: ['karasevm'] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - - type: input - id: app_version - attributes: - label: Application Version - description: What version of the app are you running? - placeholder: ex. 1.0 - validations: - required: true - - type: input - id: android_version - attributes: - label: Application Version - description: What version of Android you running? - placeholder: ex. 13 - validations: - required: true - - type: input - id: device - attributes: - label: Device - description: What device are you using? - placeholder: ex. Pixel 5 - validations: - required: true - - type: dropdown - id: install_method - attributes: - label: How do you provide the permission? - options: - - Shizuku - - ADB - - Other - validations: - required: true - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Also tell us, what did you expect to happen? - placeholder: A bug happened! - validations: - required: true - - type: textarea - id: steps-to-reproduce - attributes: - label: Steps to reproduce - description: | - Please describe what you did to reproduce the bug. - - type: textarea - id: logs - attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. - render: shell - - type: textarea - id: screens - attributes: - label: Screenshots - description: If applicable, add screenshots to help explain your problem. - - type: textarea - id: additional-context - attributes: - label: Additional context - description: Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/02-feature-request.yml b/.github/ISSUE_TEMPLATE/02-feature-request.yml deleted file mode 100644 index 7acccf3..0000000 --- a/.github/ISSUE_TEMPLATE/02-feature-request.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Feature request -description: Suggest an idea for this project. -labels: ["enhancement"] -assignees: ["karasevm"] -body: - - type: textarea - id: problem - attributes: - label: Is your feature request related to a problem? Please describe. - description: A clear and concise description of what the problem is. - placeholder: I'm always frustrated when [...] - validations: - required: true - - type: textarea - id: solution - attributes: - label: Describe the solution you'd like. - description: A clear and concise description of what you want to happen. - validations: - required: true - - type: textarea - id: alternative - attributes: - label: Describe alternatives you've considered. - description: A clear and concise description of any alternative solutions or features you've considered. - - type: textarea - id: additional-context - attributes: - label: Additional context - description: Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/03-other.yml b/.github/ISSUE_TEMPLATE/03-other.yml deleted file mode 100644 index 6ec8a5e..0000000 --- a/.github/ISSUE_TEMPLATE/03-other.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Other -description: If other options don't fit your question. -body: - - type: textarea - id: other - attributes: - label: Ask a question - validations: - required: true diff --git a/.gitignore b/.gitignore index 8d9ff13..69eda01 100644 --- a/.gitignore +++ b/.gitignore @@ -3,15 +3,6 @@ *.ap_ *.aab -# Android Studio generated files and folders -captures/ -.externalNativeBuild/ -.cxx/ -output.json - -# Release dir -app/release/* - # Files for the ART/Dalvik VM *.dex @@ -39,16 +30,23 @@ proguard/ # Android Studio Navigation editor temp files .navigation/ +# Android Studio captures folder +captures/ + # IntelliJ *.iml -.idea/ -misc.xml -deploymentTargetDropDown.xml -render.experimental.xml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/caches # Keystore files -*.jks -*.keystore +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild @@ -67,8 +65,3 @@ fastlane/Preview.html fastlane/screenshots fastlane/test_output fastlane/readme.md - -# kotlin -.kotlin/ - -*~ diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b86273d..fb7f4a8 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index d4b7acc..0000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..bbbac72 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 57ac435..152e940 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,12 @@ -[![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 a single tap. -![Private DNS app screenshot](readme.jpg) +![Private DNS app screenshot](readme.png) ## Installation -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). +Get the latest apk on the [releases page](https://github.com/karasevm/PrivateDNSAndroid/releases/latest). -### Automatic (Shizuku) -1. Install and start [Shizuku](https://shizuku.rikka.app/). -2. Start the app and allow Shizuku access when prompted. - -### 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). @@ -32,18 +21,3 @@ 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 - -Translation status - - -### 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) diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..f9f11e3 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdk 33 + + defaultConfig { + applicationId "ru.karasevm.privatednstoggle" + minSdk 21 + targetSdk 33 + versionCode 4 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + targetSdkVersion 33 + minSdkVersion 28 + } + buildFeatures { + viewBinding true + } + buildTypes { + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + namespace 'ru.karasevm.privatednstoggle' +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts deleted file mode 100644 index 8dd2a67..0000000 --- a/app/build.gradle.kts +++ /dev/null @@ -1,94 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -plugins { - id("com.android.application") - id("kotlin-android") - id("com.google.devtools.ksp") - id("org.jetbrains.kotlin.plugin.serialization") -} - -android { - compileSdk = 36 - androidResources { - generateLocaleConfig = true - } - defaultConfig { - applicationId = "ru.karasevm.privatednstoggle" - versionCode = 18 - versionName = "1.10.0" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - targetSdk = 36 - 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 - } - - dependenciesInfo { - // Disables dependency metadata when building APKs. - includeInApk = false - // Disables dependency metadata when building Android App Bundles. - includeInBundle = false - } - namespace = "ru.karasevm.privatednstoggle" -} - -kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 - } -} - -dependencies { - implementation("androidx.core:core-ktx:1.16.0") - implementation("androidx.appcompat:appcompat:1.7.1") - implementation("androidx.recyclerview:recyclerview:1.4.0") - implementation("androidx.activity:activity-ktx:1.10.1") - implementation("androidx.fragment:fragment-ktx:1.8.8") - implementation("com.google.android.material:material:1.12.0") - implementation("androidx.constraintlayout:constraintlayout:2.2.1") - implementation("com.google.guava:guava:33.4.8-android") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") - - val shizukuVersion = "13.1.5" - implementation("dev.rikka.shizuku:api:$shizukuVersion") - implementation("dev.rikka.shizuku:provider:$shizukuVersion") - compileOnly("dev.rikka.hidden:stub:4.4.0") - - implementation("org.lsposed.hiddenapibypass:hiddenapibypass:6.1") - - // Room components - val roomVersion = "2.7.2" - implementation("androidx.room:room-ktx:$roomVersion") - ksp("androidx.room:room-compiler:$roomVersion") - androidTestImplementation("androidx.room:room-testing:$roomVersion") - - // Lifecycle components - val lifecycleVersion = "2.9.1" - 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") -} \ No newline at end of file diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..45a0364 --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "ru.karasevm.privatednstoggle", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 21da365..ff64db1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,53 +3,34 @@ xmlns:tools="http://schemas.android.com/tools"> - - - + android:theme="@style/Theme.MyApplication"> + - - - - - - - - - + + \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt b/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt new file mode 100644 index 0000000..2b24664 --- /dev/null +++ b/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt @@ -0,0 +1,75 @@ +package ru.karasevm.privatednstoggle + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import ru.karasevm.privatednstoggle.databinding.DialogAddBinding + + +class AddServerDialogFragment : DialogFragment() { + // Use this instance of the interface to deliver action events + private lateinit var listener: NoticeDialogListener + + private var _binding: DialogAddBinding? = null + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding!! + + /* The activity that creates an instance of this dialog fragment must + * implement this interface in order to receive event callbacks. + * Each method passes the DialogFragment in case the host needs to query it. */ + interface NoticeDialogListener { + fun onDialogPositiveClick(dialog: DialogFragment, server: String) + } + + // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener + override fun onAttach(context: Context) { + super.onAttach(context) + // Verify that the host activity implements the callback interface + try { + // Instantiate the NoticeDialogListener so we can send events to the host + listener = context as NoticeDialogListener + } catch (e: ClassCastException) { + // The activity doesn't implement the interface, throw exception + throw ClassCastException( + (context.toString() + + " must implement NoticeDialogListener") + ) + } + } + + override fun onCreateDialog( + savedInstanceState: Bundle? + ): Dialog { + return activity?.let { + val builder = AlertDialog.Builder(it) + // Get the layout inflater + val inflater = requireActivity().layoutInflater + _binding = DialogAddBinding.inflate(inflater) + + val view = binding.root + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + builder.setTitle(R.string.add_server) + .setView(view) + // Add action buttons + .setPositiveButton(R.string.add + ) { _, _ -> + listener.onDialogPositiveClick( + this, + binding.editTextServerAddr.text.toString() + ) + } + .setNegativeButton(R.string.cancel + ) { _, _ -> + dialog?.cancel() + } + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/ui/DeleteServerDialogFragment.kt b/app/src/main/java/ru/karasevm/privatednstoggle/DeleteServerDialogFragment.kt similarity index 64% rename from app/src/main/java/ru/karasevm/privatednstoggle/ui/DeleteServerDialogFragment.kt rename to app/src/main/java/ru/karasevm/privatednstoggle/DeleteServerDialogFragment.kt index a6a2d98..527324d 100644 --- a/app/src/main/java/ru/karasevm/privatednstoggle/ui/DeleteServerDialogFragment.kt +++ b/app/src/main/java/ru/karasevm/privatednstoggle/DeleteServerDialogFragment.kt @@ -1,14 +1,13 @@ -package ru.karasevm.privatednstoggle.ui +package ru.karasevm.privatednstoggle import android.app.Dialog import android.content.Context import android.os.Bundle +import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import ru.karasevm.privatednstoggle.R -class DeleteServerDialogFragment(private val id: Int) : DialogFragment() { +class DeleteServerDialogFragment(private val position: Int): DialogFragment() { // Use this instance of the interface to deliver action events private lateinit var listener: NoticeDialogListener @@ -16,7 +15,7 @@ class DeleteServerDialogFragment(private val id: Int) : DialogFragment() { * implement this interface in order to receive event callbacks. * Each method passes the DialogFragment in case the host needs to query it. */ interface NoticeDialogListener { - fun onDeleteDialogPositiveClick(id: Int) + fun onDialogPositiveClick(dialog: DialogFragment, position: Int) } // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener @@ -28,26 +27,20 @@ class DeleteServerDialogFragment(private val id: Int) : DialogFragment() { listener = context as NoticeDialogListener } catch (e: ClassCastException) { // The activity doesn't implement the interface, throw exception - throw ClassCastException( - (context.toString() + - " must implement NoticeDialogListener") - ) + throw ClassCastException((context.toString() + + " must implement NoticeDialogListener")) } } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { - val builder = MaterialAlertDialogBuilder(it) + val builder = AlertDialog.Builder(it) - builder.setTitle(R.string.delete_question) - .setMessage(R.string.delete_message) - .setPositiveButton( - R.string.delete + builder.setMessage(R.string.delete_question) + .setPositiveButton(R.string.delete ) { _, _ -> - listener.onDeleteDialogPositiveClick(id) + listener.onDialogPositiveClick(this, position) } - .setNegativeButton( - R.string.cancel + .setNegativeButton(R.string.cancel ) { _, _ -> dialog?.cancel() } diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/DnsTileService.kt b/app/src/main/java/ru/karasevm/privatednstoggle/DnsTileService.kt new file mode 100644 index 0000000..5e6199d --- /dev/null +++ b/app/src/main/java/ru/karasevm/privatednstoggle/DnsTileService.kt @@ -0,0 +1,195 @@ +package ru.karasevm.privatednstoggle + +import android.Manifest +import android.content.pm.PackageManager +import android.graphics.drawable.Icon +import android.provider.Settings +import android.service.quicksettings.Tile +import android.service.quicksettings.TileService +import android.util.Log +import android.widget.Toast + + +const val DNS_MODE_OFF = "off" +const val DNS_MODE_AUTO = "opportunistic" +const val DNS_MODE_PRIVATE = "hostname" + +class DnsTileService : TileService() { + + + private fun checkForPermission(): Boolean { + if (checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED) { + return true + } + Toast.makeText(this, R.string.permission_missing, Toast.LENGTH_SHORT).show() + return false + } + + override fun onTileAdded() { + super.onTileAdded() + checkForPermission() + // Update state + qsTile.state = Tile.STATE_INACTIVE + + // Update looks + qsTile.updateTile() + } + + override fun onClick() { + super.onClick() + if (!checkForPermission()) { + return + } + + val dnsMode = Settings.Global.getString(contentResolver, "private_dns_mode") + val dnsProvider = Settings.Global.getString(contentResolver, "private_dns_specifier") + + if (dnsMode.equals(DNS_MODE_OFF, ignoreCase = true)) { +// refreshTile(qsTile, Tile.STATE_INACTIVE, getString(R.string.dns_off), R.drawable.ic_off_black_24dp) + changeTileState( + qsTile, + Tile.STATE_ACTIVE, + getNextAddress(dnsProvider), + R.drawable.ic_private_black_24dp, + DNS_MODE_PRIVATE, + getNextAddress(dnsProvider) + ) + } else if (dnsMode == null || dnsMode.equals(DNS_MODE_AUTO, ignoreCase = true)) { + changeTileState( + qsTile, + Tile.STATE_ACTIVE, + getNextAddress(dnsProvider), + R.drawable.ic_private_black_24dp, + DNS_MODE_PRIVATE, + getNextAddress(dnsProvider) + ) + } else if (dnsMode.equals(DNS_MODE_PRIVATE, ignoreCase = true)) { + if (getNextAddress(dnsProvider) == null) { + changeTileState( + qsTile, + Tile.STATE_INACTIVE, + getString(R.string.dns_off), + R.drawable.ic_off_black_24dp, + DNS_MODE_OFF, + getNextAddress(dnsProvider) + ) + } else { + changeTileState( + qsTile, + Tile.STATE_ACTIVE, + getNextAddress(dnsProvider), + R.drawable.ic_private_black_24dp, + DNS_MODE_PRIVATE, + getNextAddress(dnsProvider) + ) + } + } + + } + + override fun onStartListening() { + super.onStartListening() + if (!checkForPermission()) { + return + } + val dnsMode = Settings.Global.getString(contentResolver, "private_dns_mode") + Log.d("TEMP", "onStartListening: called $dnsMode") + if (dnsMode.equals(DNS_MODE_OFF, ignoreCase = true)) { + refreshTile( + qsTile, + Tile.STATE_INACTIVE, + getString(R.string.dns_off), + R.drawable.ic_off_black_24dp + ) + } else if (dnsMode == null || dnsMode.equals(DNS_MODE_AUTO, ignoreCase = true)) { + refreshTile( + qsTile, + Tile.STATE_INACTIVE, + getString(R.string.dns_auto), + R.drawable.ic_auto_black_24dp + ) + } else if (dnsMode.equals(DNS_MODE_PRIVATE, ignoreCase = true)) { + val dnsProvider = Settings.Global.getString(contentResolver, "private_dns_specifier") + if (dnsProvider != null) { + refreshTile( + qsTile, + Tile.STATE_ACTIVE, + dnsProvider, + R.drawable.ic_private_black_24dp + ) + } else { + Toast.makeText(this, R.string.permission_missing, Toast.LENGTH_SHORT).show() + } + } + + } + + /** + * Updates tile to specified parameters + * + * @param tile tile to update + * @param state tile state + * @param label tile label + * @param icon tile icon + */ + private fun refreshTile(tile: Tile, state: Int, label: String?, icon: Int) { + tile.state = state + tile.label = label + tile.icon = Icon.createWithResource(this, icon) + tile.updateTile() + } + + /** + * Updates tile and system settings to specified parameters + * + * @param tile tile to update + * @param state tile state + * @param label tile label + * @param icon tile icon + * @param dnsMode system dns mode + * @param dnsProvider system dns provider + */ + private fun changeTileState( + tile: Tile, + state: Int, + label: String?, + icon: Int, + dnsMode: String, + dnsProvider: String? + ) { + tile.label = label + tile.state = state + tile.icon = Icon.createWithResource(this, icon) + Settings.Global.putString(contentResolver, "private_dns_mode", dnsMode) + Settings.Global.putString(contentResolver, "private_dns_specifier", dnsProvider) + tile.updateTile() + } + + /** + * Gets next dns address from preferences, + * if current address is last returns null + * + * @param currentAddress currently set address + * @return next address + */ + private fun getNextAddress(currentAddress: String?): String? { + val sharedPrefs = this.getSharedPreferences("app_prefs", 0) + val items = sharedPrefs.getString("dns_servers", "dns.google")!!.split(",").toMutableList() + + // Fallback if list is empty + if (items[0] == "") { + items.removeAt(0) + items.add("dns.google") + } + + val index = items.indexOf(currentAddress) + + if (index == -1 || currentAddress == null) { + return items[0] + } + if (index == items.size - 1) { + return null + } + return items[index + 1] + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt b/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt new file mode 100644 index 0000000..bddc079 --- /dev/null +++ b/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt @@ -0,0 +1,100 @@ +package ru.karasevm.privatednstoggle + +import android.Manifest +import android.content.SharedPreferences +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.widget.Toast +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import ru.karasevm.privatednstoggle.databinding.ActivityMainBinding + + +class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogListener, DeleteServerDialogFragment.NoticeDialogListener { + + private lateinit var linearLayoutManager: LinearLayoutManager + private lateinit var binding: ActivityMainBinding + private var items = mutableListOf() + private lateinit var sharedPrefs: SharedPreferences + private lateinit var adapter: RecyclerAdapter + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) + + 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() + } + linearLayoutManager = LinearLayoutManager(this) + binding.recyclerView.layoutManager = linearLayoutManager + + sharedPrefs = this.getSharedPreferences("app_prefs", 0) + + items = sharedPrefs.getString("dns_servers", "")!!.split(",").toMutableList() + if (items[0] == "") { + items.removeAt(0) + } + adapter = RecyclerAdapter(items) + adapter.onItemClick = { position -> + val newFragment = DeleteServerDialogFragment(position) + newFragment.show(supportFragmentManager, "delete_server") + } + binding.recyclerView.adapter = adapter + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.add_server -> { + val newFragment = AddServerDialogFragment() + newFragment.show(supportFragmentManager, "add_server") + true + } + R.id.privacy_policy -> { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://karasevm.github.io/PrivateDNSAndroid/privacy_policy")) + startActivity(browserIntent) + true + } + + else -> { + // If we got here, the user's action was not recognized. + // Invoke the superclass to handle it. + super.onOptionsItemSelected(item) + } + } + + override fun onDialogPositiveClick(dialog: DialogFragment, server: String) { + if (server.isEmpty()) { + Toast.makeText(this, R.string.server_length_error, Toast.LENGTH_SHORT).show() + return + } + items.add(server) + adapter.setData(items.toMutableList()) + binding.recyclerView.adapter?.notifyItemInserted(items.size - 1) + sharedPrefs.edit() + .putString("dns_servers", items.joinToString(separator = ",") { it }).apply() + } + + override fun onDialogPositiveClick(dialog: DialogFragment,position: Int) { + items.removeAt(position) + adapter.setData(items.toMutableList()) + adapter.notifyItemRemoved(position) + sharedPrefs.edit() + .putString("dns_servers", items.joinToString(separator = ",") { it }).apply() + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/PrivateDNSApp.kt b/app/src/main/java/ru/karasevm/privatednstoggle/PrivateDNSApp.kt deleted file mode 100644 index c404503..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/PrivateDNSApp.kt +++ /dev/null @@ -1,33 +0,0 @@ -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 - -class PrivateDNSApp : Application() { - - private val database by lazy { DnsServerRoomDatabase.getDatabase(this) } - val repository by lazy { DnsServerRepository(database.dnsServerDao()) } - - 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() - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt b/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt new file mode 100644 index 0000000..e0949a8 --- /dev/null +++ b/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt @@ -0,0 +1,50 @@ +package ru.karasevm.privatednstoggle + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import java.util.* + +class RecyclerAdapter(val items: MutableList): RecyclerView.Adapter() { + + var onItemClick: ((Int) -> Unit)? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerAdapter.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_row, parent, false) + val vh = ViewHolder(view) + return vh + } + + override fun onBindViewHolder(holder: RecyclerAdapter.ViewHolder, position: Int) { + val item = items[position] + + // sets the text to the textview from our itemHolder class + holder.textView.text = item + } + + override fun getItemCount(): Int { + return items.size + } + + + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + init { + itemView.setOnClickListener { + onItemClick?.invoke(adapterPosition) + } + } + } + fun setData(newItems: MutableList) { + items.run { + clear() + addAll(newItems) + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/data/DnsServerDao.kt b/app/src/main/java/ru/karasevm/privatednstoggle/data/DnsServerDao.kt deleted file mode 100644 index 026de12..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/data/DnsServerDao.kt +++ /dev/null @@ -1,84 +0,0 @@ -package ru.karasevm.privatednstoggle.data - -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Transaction -import kotlinx.coroutines.flow.Flow -import ru.karasevm.privatednstoggle.model.DnsServer - -@Dao -interface DnsServerDao { - - @Query("SELECT * FROM dns_servers ORDER BY sortOrder ASC") - fun getAll(): Flow> - - @Query("SELECT * FROM dns_servers WHERE enabled = 1 ORDER BY sortOrder ASC LIMIT 1") - suspend fun getFirstEnabled(): DnsServer - - @Query("SELECT * FROM dns_servers WHERE server = :server LIMIT 1") - suspend fun getFirstByServer(server: String): DnsServer? - - @Query("SELECT * FROM dns_servers WHERE id = :id") - suspend fun getById(id: Int): DnsServer? - - @Query("SELECT * FROM dns_servers " + - "WHERE sortOrder > (SELECT sortOrder FROM dns_servers WHERE server = :server) AND enabled = 1 " + - "ORDER BY sortOrder ASC " + - "LIMIT 1") - suspend fun getNextEnabledByServer(server: String): DnsServer? - - @Query("DELETE FROM dns_servers") - suspend fun deleteAll() - - @Query("DELETE FROM dns_servers WHERE id = :id") - suspend fun deleteById(id: Int) - - @Query("UPDATE dns_servers SET sortOrder = sortOrder + 1 " + - "WHERE sortOrder >= :startSortOrder AND sortOrder <= :endSortOrder") - suspend fun incrementSortOrder(startSortOrder: Int, endSortOrder: Int = Int.MAX_VALUE) - - @Query("UPDATE dns_servers SET sortOrder = sortOrder - 1 " + - "WHERE sortOrder >= :startSortOrder AND sortOrder <= :endSortOrder") - suspend fun decrementSortOrder(startSortOrder: Int, endSortOrder: Int = Int.MAX_VALUE) - - @Query("UPDATE dns_servers SET sortOrder = sortOrder - 1 " + - "WHERE sortOrder > (SELECT sortOrder FROM dns_servers WHERE id = :id)") - suspend fun decrementSortOrderById(id: Int) - - @Transaction - suspend fun deleteAndDecrement(id: Int) { - decrementSortOrderById(id) - deleteById(id) - } - - @Query("UPDATE dns_servers SET label = :label WHERE id = :id") - suspend fun updateLabel(id: Int, label: String) - - @Query("UPDATE dns_servers SET server = :server WHERE id = :id") - suspend fun updateServer(id: Int, server: String) - - @Query("UPDATE dns_servers " + - "SET server = COALESCE(:server, server), " + - " label = COALESCE(:label, label), " + - " sortOrder = COALESCE(:sortOrder, sortOrder), " + - " enabled = COALESCE(:enabled, enabled) " + - "WHERE id = :id") - suspend fun update(id: Int, server: String?, label: String?, sortOrder: Int?, enabled: Boolean?) - - @Transaction - suspend fun moveUp(sortOrder: Int, newSortOrder: Int, id: Int){ - incrementSortOrder(newSortOrder, sortOrder) - update(id, null, null, newSortOrder, null) - } - - @Transaction - suspend fun moveDown(sortOrder: Int, newSortOrder: Int, id: Int){ - decrementSortOrder(sortOrder, newSortOrder) - update(id, null, null, newSortOrder, null) - } - - @Query("INSERT INTO dns_servers(server, label, sortOrder, enabled) " + - "VALUES(:server, :label, COALESCE((SELECT MAX(sortOrder) + 1 FROM dns_servers), 0), :enabled)") - suspend fun insert(server: String, label: String, enabled: Boolean) - -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/data/DnsServerRepository.kt b/app/src/main/java/ru/karasevm/privatednstoggle/data/DnsServerRepository.kt deleted file mode 100644 index 4c7ddd4..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/data/DnsServerRepository.kt +++ /dev/null @@ -1,60 +0,0 @@ -package ru.karasevm.privatednstoggle.data - -import androidx.annotation.WorkerThread -import kotlinx.coroutines.flow.Flow -import ru.karasevm.privatednstoggle.model.DnsServer - -class DnsServerRepository(private val dnsServerDao: DnsServerDao) { - - val allServers: Flow> = dnsServerDao.getAll() - - @WorkerThread - fun getAll() = dnsServerDao.getAll() - - @WorkerThread - suspend fun getFirstEnabled() = dnsServerDao.getFirstEnabled() - - @WorkerThread - suspend fun getById(id: Int) = dnsServerDao.getById(id) - - @WorkerThread - suspend fun getFirstByServer(server: String) = dnsServerDao.getFirstByServer(server) - - @WorkerThread - suspend fun getNextByServer(server: String) = dnsServerDao.getNextEnabledByServer(server) - - @WorkerThread - suspend fun insert(dnsServer: DnsServer) { - dnsServerDao.insert(dnsServer.server, dnsServer.label, dnsServer.enabled) - } - - @WorkerThread - suspend fun update( - id: Int, - server: String?, - label: String?, - sortOrder: Int?, - enabled: Boolean? - ) { - dnsServerDao.update(id, server, label, sortOrder, enabled) - } - - @WorkerThread - suspend fun move(sortOrder: Int, newSortOrder: Int, id: Int) { - if (sortOrder == newSortOrder) { - return - } - if (newSortOrder > sortOrder) { - dnsServerDao.moveDown(sortOrder, newSortOrder, id) - } else { - dnsServerDao.moveUp(sortOrder, newSortOrder, id) - } - } - - @WorkerThread - suspend fun delete(id: Int) { - dnsServerDao.deleteAndDecrement(id) - } - - -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/data/DnsServerViewModel.kt b/app/src/main/java/ru/karasevm/privatednstoggle/data/DnsServerViewModel.kt deleted file mode 100644 index bd1987a..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/data/DnsServerViewModel.kt +++ /dev/null @@ -1,48 +0,0 @@ -package ru.karasevm.privatednstoggle.data - -import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.asLiveData -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import ru.karasevm.privatednstoggle.model.DnsServer - -class DnsServerViewModel(private val dnsServerRepository: DnsServerRepository) : ViewModel() { - - val allServers: LiveData> = dnsServerRepository.allServers.asLiveData() - - fun getAll() = dnsServerRepository.getAll() - - suspend fun getById(id: Int) = dnsServerRepository.getById(id) - - fun insert(dnsServer: DnsServer) = - viewModelScope.launch { - dnsServerRepository.insert(dnsServer) - } - - fun update( - id: Int, - server: String? = null, - label: String? = null, - sortOrder: Int? = null, - enabled: Boolean? = null - ) = viewModelScope.launch { dnsServerRepository.update(id, server, label, sortOrder, enabled) } - - fun move(sortOrder: Int, newSortOrder: Int, id: Int) = - viewModelScope.launch { dnsServerRepository.move(sortOrder, newSortOrder, id) } - - fun delete(id: Int) = viewModelScope.launch { dnsServerRepository.delete(id) } - -} - -class DnsServerViewModelFactory(private val dnsServerRepository: DnsServerRepository) : - ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(DnsServerViewModel::class.java)) { - @Suppress("UNCHECKED_CAST") - return DnsServerViewModel(dnsServerRepository) as T - } - throw IllegalArgumentException("Unknown ViewModel class") - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/data/database/DnsServerRoomDatabase.kt b/app/src/main/java/ru/karasevm/privatednstoggle/data/database/DnsServerRoomDatabase.kt deleted file mode 100644 index 72b6f25..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/data/database/DnsServerRoomDatabase.kt +++ /dev/null @@ -1,34 +0,0 @@ -package ru.karasevm.privatednstoggle.data.database - -import android.content.Context -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase -import ru.karasevm.privatednstoggle.data.DnsServerDao -import ru.karasevm.privatednstoggle.model.DnsServer - -@Database(entities = [DnsServer::class], version = 1, exportSchema = false) -abstract class DnsServerRoomDatabase : RoomDatabase() { - - abstract fun dnsServerDao(): DnsServerDao - - companion object { - @Volatile - private var INSTANCE: DnsServerRoomDatabase? = null - fun getDatabase(context: Context): DnsServerRoomDatabase { - val tempInstance = INSTANCE - if (tempInstance != null) { - return tempInstance - } - synchronized(this) { - val instance = Room.databaseBuilder( - context.applicationContext, - DnsServerRoomDatabase::class.java, - "dns_server_database" - ).build() - INSTANCE = instance - return instance - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/model/DnsServer.kt b/app/src/main/java/ru/karasevm/privatednstoggle/model/DnsServer.kt deleted file mode 100644 index 38c1be4..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/model/DnsServer.kt +++ /dev/null @@ -1,24 +0,0 @@ -package ru.karasevm.privatednstoggle.model - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -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( - @SerialName("id") - @PrimaryKey(autoGenerate = true) - val id: Int = 0, - @SerialName("server") - val server: String = "", - @SerialName("label") - val label: String = "", - @SerialName("enabled") - @ColumnInfo(defaultValue = "1") - val enabled: Boolean = true, - val sortOrder: Int? = null -) diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/service/DnsTileService.kt b/app/src/main/java/ru/karasevm/privatednstoggle/service/DnsTileService.kt deleted file mode 100644 index 1bb1944..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/service/DnsTileService.kt +++ /dev/null @@ -1,302 +0,0 @@ -package ru.karasevm.privatednstoggle.service - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -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 -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelChildren -import kotlinx.coroutines.launch -import ru.karasevm.privatednstoggle.PrivateDNSApp -import ru.karasevm.privatednstoggle.R -import ru.karasevm.privatednstoggle.data.DnsServerRepository -import ru.karasevm.privatednstoggle.model.DnsServer -import ru.karasevm.privatednstoggle.util.PreferenceHelper -import ru.karasevm.privatednstoggle.util.PreferenceHelper.autoMode -import ru.karasevm.privatednstoggle.util.PreferenceHelper.requireUnlock -import ru.karasevm.privatednstoggle.util.PrivateDNSUtils -import ru.karasevm.privatednstoggle.util.PrivateDNSUtils.AUTO_MODE_OPTION_AUTO -import ru.karasevm.privatednstoggle.util.PrivateDNSUtils.AUTO_MODE_OPTION_OFF_AUTO -import ru.karasevm.privatednstoggle.util.PrivateDNSUtils.AUTO_MODE_OPTION_PRIVATE -import ru.karasevm.privatednstoggle.util.PrivateDNSUtils.DNS_MODE_AUTO -import ru.karasevm.privatednstoggle.util.PrivateDNSUtils.DNS_MODE_OFF -import ru.karasevm.privatednstoggle.util.PrivateDNSUtils.DNS_MODE_PRIVATE -import ru.karasevm.privatednstoggle.util.PrivateDNSUtils.checkForPermission - -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() - checkForPermission(this) - // Update state - qsTile.state = Tile.STATE_INACTIVE - - // Update looks - qsTile.updateTile() - } - - /** - * Set's the state of the tile and system settings to the next state - */ - private fun cycleState() { - val dnsMode = Settings.Global.getString(contentResolver, "private_dns_mode") - val dnsProvider = Settings.Global.getString(contentResolver, "private_dns_specifier") - - if (dnsMode.equals(DNS_MODE_OFF, ignoreCase = true)) { - 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) - } - - } else if (dnsMode == null || dnsMode.equals(DNS_MODE_AUTO, ignoreCase = true)) { - changeDNSServer(DNS_MODE_PRIVATE, null) - } else if (dnsMode.equals(DNS_MODE_PRIVATE, ignoreCase = true)) { - scope.launch { - if (getNextAddress(dnsProvider) == null) { - if (sharedPreferences.autoMode == AUTO_MODE_OPTION_PRIVATE) { - changeDNSServer(DNS_MODE_PRIVATE, null) - } else { - if (sharedPreferences.autoMode == AUTO_MODE_OPTION_AUTO) { - changeDNSServer(DNS_MODE_AUTO, dnsProvider) - } else { - changeDNSServer(DNS_MODE_OFF, dnsProvider) - } - } - } else { - changeDNSServer(DNS_MODE_PRIVATE, dnsProvider) - } - } - } - } - - /** - * Sets the state of the tile to the provided values - * @param mode dns mode - * @param dnsProvider dns provider - */ - private fun changeDNSServer(mode: String, dnsProvider: String?) { - when (mode) { - DNS_MODE_OFF -> { - changeTileState( - qsTile, - Tile.STATE_INACTIVE, - getString(R.string.dns_off), - R.drawable.ic_off_black_24dp, - DNS_MODE_OFF, - null - ) - } - - DNS_MODE_AUTO -> { - changeTileState( - qsTile, - Tile.STATE_INACTIVE, - getString(R.string.dns_auto), - R.drawable.ic_auto_black_24dp, - DNS_MODE_AUTO, - dnsProvider - ) - } - - DNS_MODE_PRIVATE -> { - scope.launch { - val nextDnsServer = getNextAddress(dnsProvider) - if (nextDnsServer != null) { - changeTileState( - qsTile, - Tile.STATE_ACTIVE, - nextDnsServer.label.ifEmpty { nextDnsServer.server }, - R.drawable.ic_private_black_24dp, - DNS_MODE_PRIVATE, - getNextAddress(dnsProvider)?.server - ) - } - } - } - } - } - - override fun onClick() { - super.onClick() - if (!checkForPermission(this)) { - return - } - - // Require unlock to change mode according to user preference - val requireUnlock = sharedPreferences.requireUnlock - if (isLocked && requireUnlock) { - unlockAndRun(this::cycleState) - } else { - cycleState() - } - - - } - - /** - * 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, - if (!isPermissionGranted) Tile.STATE_UNAVAILABLE else Tile.STATE_INACTIVE, - getString(R.string.dns_off), - R.drawable.ic_off_black_24dp - ) - } - - DNS_MODE_AUTO -> { - setTile( - qsTile, - if (!isPermissionGranted) Tile.STATE_UNAVAILABLE else Tile.STATE_INACTIVE, - getString(R.string.dns_auto), - R.drawable.ic_auto_black_24dp - ) - } - - DNS_MODE_PRIVATE -> { - scope.launch { - val activeAddress = - Settings.Global.getString(contentResolver, "private_dns_specifier") - val dnsServer = repository.getFirstByServer(activeAddress) - setTile( - qsTile, - 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 - ) - } - } - - else -> { - setTile( - qsTile, - if (!isPermissionGranted) Tile.STATE_UNAVAILABLE else Tile.STATE_INACTIVE, - getString(R.string.dns_unknown), - R.drawable.ic_unknown_black_24dp - ) - } - } - } - - private val broadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - refreshTile() - } - } - - override fun onStartListening() { - super.onStartListening() - - // Prevent some crashes - if (qsTile == null) { - Log.w(TAG, "onStartListening: qsTile is null") - return - } - - - // Receive broadcasts to update the tile when server is changed from the dialog - ContextCompat.registerReceiver( - this, - broadcastReceiver, - IntentFilter("refresh_tile"), - ContextCompat.RECEIVER_NOT_EXPORTED - ) - isBroadcastReceiverRegistered = true - refreshTile() - } - - override fun onStopListening() { - super.onStopListening() - if (isBroadcastReceiverRegistered) { - unregisterReceiver(broadcastReceiver) - isBroadcastReceiverRegistered = false - } - - } - - override fun onDestroy() { - super.onDestroy() - job.cancelChildren() - } - - /** - * Updates tile to specified parameters - * - * @param tile tile to update - * @param state tile state - * @param label tile label - * @param icon tile icon - */ - private fun setTile(tile: Tile, state: Int, label: String?, icon: Int) { - tile.state = state - tile.label = label - tile.icon = Icon.createWithResource(this, icon) - tile.updateTile() - } - - /** - * Updates tile and system settings to specified parameters - * - * @param tile tile to update - * @param state tile state - * @param label tile label - * @param icon tile icon - * @param dnsMode system dns mode - * @param dnsProvider system dns provider - */ - private fun changeTileState( - tile: Tile, - state: Int, - label: String?, - icon: Int, - dnsMode: String, - dnsProvider: String? - ) { - tile.label = label - tile.state = state - tile.icon = Icon.createWithResource(this, icon) - PrivateDNSUtils.setPrivateMode(contentResolver, dnsMode) - PrivateDNSUtils.setPrivateProvider(contentResolver, dnsProvider) - tile.updateTile() - } - - /** - * Gets next dns address from the database, - * if current address is last or unknown returns null - * - * @param currentAddress currently set address - * @return next address - */ - private suspend fun getNextAddress(currentAddress: String?): DnsServer? { - return if (currentAddress.isNullOrEmpty()) { - repository.getFirstEnabled() - } else { - repository.getNextByServer(currentAddress) - } - } - - companion object { - private const val TAG = "DnsTileService" - } -} diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/ui/AddServerDialogFragment.kt b/app/src/main/java/ru/karasevm/privatednstoggle/ui/AddServerDialogFragment.kt deleted file mode 100644 index f5440d9..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/ui/AddServerDialogFragment.kt +++ /dev/null @@ -1,140 +0,0 @@ -package ru.karasevm.privatednstoggle.ui - -import android.app.Dialog -import android.content.Context -import android.content.DialogInterface -import android.os.Bundle -import android.text.Editable -import android.text.TextUtils -import android.text.TextWatcher -import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.common.net.InternetDomainName -import ru.karasevm.privatednstoggle.R -import ru.karasevm.privatednstoggle.databinding.DialogAddBinding -import ru.karasevm.privatednstoggle.model.DnsServer - - -class AddServerDialogFragment( - private val dnsServer: DnsServer? -) : DialogFragment() { - // Use this instance of the interface to deliver action events - private lateinit var listener: NoticeDialogListener - - private var _binding: DialogAddBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - /* The activity that creates an instance of this dialog fragment must - * implement this interface in order to receive event callbacks. - * Each method passes the DialogFragment in case the host needs to query it. */ - interface NoticeDialogListener { - fun onAddDialogPositiveClick(label: String?, server: String) - fun onUpdateDialogPositiveClick(id: Int, server: String, label: String?, enabled: Boolean) - fun onDeleteItemClicked(id: Int) - } - - // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener - override fun onAttach(context: Context) { - super.onAttach(context) - // Verify that the host activity implements the callback interface - try { - // Instantiate the NoticeDialogListener so we can send events to the host - listener = context as NoticeDialogListener - } catch (e: ClassCastException) { - // The activity doesn't implement the interface, throw exception - throw ClassCastException( - (context.toString() + - " must implement NoticeDialogListener") - ) - } - } - - override fun onCreateDialog( - savedInstanceState: Bundle? - ): Dialog { - return activity?.let { - val builder = MaterialAlertDialogBuilder(it) - // Get the layout inflater - val inflater = requireActivity().layoutInflater - _binding = DialogAddBinding.inflate(inflater) - - val view = binding.root - // Inflate and set the layout for the dialog - // Pass null as the parent view because its going in the dialog layout - if (dnsServer != null) { - binding.editTextServerHint.setText(dnsServer.label) - binding.editTextServerAddr.setText(dnsServer.server) - binding.serverEnabledSwitch.visibility = android.view.View.VISIBLE - binding.serverEnabledSwitch.isChecked = dnsServer.enabled - builder.setTitle(R.string.edit_server).setView(view) - .setPositiveButton( - R.string.menu_save - ) { _, _ -> - listener.onUpdateDialogPositiveClick( - dnsServer.id, - binding.editTextServerAddr.text.toString().trim(), - binding.editTextServerHint.text.toString().trim(), - binding.serverEnabledSwitch.isChecked - ) - } - .setNegativeButton( - R.string.cancel - ) { _, _ -> - dialog?.cancel() - } - .setNeutralButton( - R.string.delete - ) { _, _ -> - listener.onDeleteItemClicked(dnsServer.id) - } - } else { - builder.setTitle(R.string.add_server) - .setView(view) - // Add action buttons - .setPositiveButton( - R.string.menu_add - ) { _, _ -> - listener.onAddDialogPositiveClick( - binding.editTextServerHint.text.toString().trim(), - binding.editTextServerAddr.text.toString().trim() - ) - } - .setNegativeButton( - R.string.cancel - ) { _, _ -> - dialog?.cancel() - } - } - builder.create() - } ?: throw IllegalStateException("Activity cannot be null") - } - - override fun onStart() { - super.onStart() - val button = ((dialog) as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE) - binding.editTextServerAddr.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - - override fun afterTextChanged(s: Editable?) { - val server = binding.editTextServerAddr.text.toString().trim() - if (TextUtils.isEmpty(server) || !isValidServer(server)) { - button.isEnabled = false - } else { - binding.editTextServerAddr.error = null - button.isEnabled = true - } - } - }) - } - - private fun isValidServer(str: String): Boolean { - return InternetDomainName.isValid(str) - } - -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/ui/DNSServerDialogFragment.kt b/app/src/main/java/ru/karasevm/privatednstoggle/ui/DNSServerDialogFragment.kt deleted file mode 100644 index b2fa284..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/ui/DNSServerDialogFragment.kt +++ /dev/null @@ -1,140 +0,0 @@ -package ru.karasevm.privatednstoggle.ui - -import android.app.Dialog -import android.content.Intent -import android.os.Bundle -import android.widget.Toast -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.coroutines.launch -import ru.karasevm.privatednstoggle.PrivateDNSApp -import ru.karasevm.privatednstoggle.R -import ru.karasevm.privatednstoggle.data.DnsServerViewModel -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() { - - private var _binding: SheetDnsSelectorBinding? = null - private val binding get() = _binding!! - - private lateinit var linearLayoutManager: LinearLayoutManager - private lateinit var adapter: ServerListRecyclerAdapter - private var servers: MutableList = 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? - ): Dialog { - return activity?.let { - val startIntent = Intent(context, MainActivity::class.java) - - val builder = MaterialAlertDialogBuilder(it) - val inflater = requireActivity().layoutInflater - _binding = SheetDnsSelectorBinding.inflate(inflater) - linearLayoutManager = LinearLayoutManager(context) - binding.recyclerView.layoutManager = linearLayoutManager - - adapter = ServerListRecyclerAdapter(false) - binding.recyclerView.adapter = adapter - lifecycleScope.launch { - dnsServerViewModel.getAll().collect { s -> - servers = s.toMutableList() - if (servers.isEmpty()) { - servers.add(DnsServer(0, "dns.google")) - } - servers.add(0, DnsServer(-1, resources.getString(R.string.dns_auto))) - servers.add(0, DnsServer(-2, resources.getString(R.string.dns_off))) - adapter.submitList(servers) - } - } - builder.setTitle(R.string.select_server) - .setView(binding.root) - .setPositiveButton( - R.string.done - ) { _, _ -> - dialog?.dismiss() - } - .setNeutralButton(R.string.open_app) { _, _ -> context?.startActivity(startIntent) } - builder.create() - } ?: throw IllegalStateException("Activity cannot be null") - } - - 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( - 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( - contentResolver, - PrivateDNSUtils.DNS_MODE_AUTO - ) - PrivateDNSUtils.setPrivateProvider( - contentResolver, - null) - Toast.makeText(context, R.string.set_to_auto_toast, Toast.LENGTH_SHORT).show() - } - - else -> { - lifecycleScope.launch { - val server = servers.find { server -> server.id == id } - PrivateDNSUtils.setPrivateMode( - contentResolver, - PrivateDNSUtils.DNS_MODE_PRIVATE - ) - PrivateDNSUtils.setPrivateProvider( - contentResolver, - server?.server - ) - Toast.makeText( - context, - getString( - R.string.set_to_provider_toast, - server?.label?.ifEmpty { server.server } - ), - Toast.LENGTH_SHORT - ).show() - } - } - } - dialog?.dismiss() - requireContext().sendBroadcast(Intent("refresh_tile").setPackage(requireContext().packageName)) - } - - } - - override fun onDestroy() { - super.onDestroy() - activity?.finish() - } - - companion object { - const val TAG = "DNSServerDialogFragment" - private const val AUTO_ID = -1 - private const val OFF_ID = -2 - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/ui/MainActivity.kt b/app/src/main/java/ru/karasevm/privatednstoggle/ui/MainActivity.kt deleted file mode 100644 index 26695a3..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/ui/MainActivity.kt +++ /dev/null @@ -1,480 +0,0 @@ -package ru.karasevm.privatednstoggle.ui - -import android.Manifest -import android.content.ClipData -import android.content.ClipDescription.MIMETYPE_TEXT_PLAIN -import android.content.ClipboardManager -import android.content.Intent -import android.content.SharedPreferences -import android.content.pm.PackageManager -import android.graphics.Color -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.util.Log -import android.view.Menu -import android.view.View -import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts -import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity -import androidx.core.app.ShareCompat -import androidx.lifecycle.viewModelScope -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.ItemTouchHelper.DOWN -import androidx.recyclerview.widget.ItemTouchHelper.UP -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import rikka.shizuku.Shizuku -import rikka.shizuku.ShizukuProvider -import ru.karasevm.privatednstoggle.PrivateDNSApp -import ru.karasevm.privatednstoggle.R -import ru.karasevm.privatednstoggle.data.DnsServerViewModel -import ru.karasevm.privatednstoggle.data.DnsServerViewModelFactory -import ru.karasevm.privatednstoggle.databinding.ActivityMainBinding -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, - DeleteServerDialogFragment.NoticeDialogListener, Shizuku.OnRequestPermissionResultListener { - - private lateinit var linearLayoutManager: LinearLayoutManager - private lateinit var binding: ActivityMainBinding - private lateinit var sharedPrefs: SharedPreferences - private lateinit var adapter: ServerListRecyclerAdapter - private lateinit var clipboard: ClipboardManager - private val dnsServerViewModel: DnsServerViewModel by viewModels { DnsServerViewModelFactory((application as PrivateDNSApp).repository) } - - private val itemTouchHelper by lazy { - val simpleItemTouchCallback = - object : ItemTouchHelper.SimpleCallback(UP or DOWN, 0) { - var dragFrom = -1 - var dragTo = -1 - - override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder - ): Boolean { - if (dragFrom == viewHolder.bindingAdapterPosition && dragTo == target.bindingAdapterPosition) { - return true - } - // store the drag position - if (dragFrom == -1) dragFrom = viewHolder.bindingAdapterPosition - dragTo = target.bindingAdapterPosition - adapter.onItemMove( - viewHolder.bindingAdapterPosition, - target.bindingAdapterPosition - ) - return true - } - - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} - - override fun onSelectedChanged( - viewHolder: RecyclerView.ViewHolder?, actionState: Int - ) { - super.onSelectedChanged(viewHolder, actionState) - if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { - viewHolder?.itemView?.apply { - // Example: Elevate the view - elevation = 8f - alpha = 0.5f - setBackgroundColor(Color.GRAY) - } - } - } - - override fun clearView( - recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder - ) { - super.clearView(recyclerView, viewHolder) - viewHolder.itemView.apply { - // Reset the appearance - elevation = 0f - alpha = 1.0f - setBackgroundColor(Color.TRANSPARENT) - } - // commit the change to the db - dnsServerViewModel.move( - dragFrom, - dragTo, - (viewHolder as ServerListRecyclerAdapter.DnsServerViewHolder).id - ) - dragTo = -1 - dragFrom = -1 - } - } - ItemTouchHelper(simpleItemTouchCallback) - } - - private fun importSettings(json: String) { - runCatching { - val data: BackupUtils.Backup = Json.decodeFromString(json) - BackupUtils.import(data, dnsServerViewModel, sharedPrefs) - }.onSuccess { - Toast.makeText( - this, getString(R.string.import_success), Toast.LENGTH_SHORT - ).show() - }.onFailure { exception -> - runCatching { - Log.e("IMPORT", "Malformed json, falling back to legacy", exception) - val data = Json.decodeFromString(json) - BackupUtils.importLegacy(data, dnsServerViewModel, sharedPrefs) - }.onSuccess { - Toast.makeText( - this, getString(R.string.import_success), Toast.LENGTH_SHORT - ).show() - }.onFailure { exception -> - Log.e("IMPORT", "Import failed", exception) - Toast.makeText( - this, getString(R.string.import_failure), Toast.LENGTH_SHORT - ).show() - } - } - } - - /** - * Migrate the SharedPreferences server list to Room - */ - private fun migrateServerList() { - 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().toMutableList() - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - Shizuku.addRequestPermissionResultListener(this::onRequestPermissionResult) - - binding = ActivityMainBinding.inflate(layoutInflater) - val view = binding.root - setContentView(view) - - linearLayoutManager = LinearLayoutManager(this) - binding.recyclerView.layoutManager = linearLayoutManager - - sharedPrefs = PreferenceHelper.defaultPreference(this) - clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager - - migrateServerList() - - adapter = ServerListRecyclerAdapter(true) - binding.recyclerView.adapter = adapter - - dnsServerViewModel.allServers.observe(this) { servers -> - adapter.submitList(servers) - if (servers.isEmpty()) { - binding.emptyView.visibility = View.VISIBLE - binding.emptyViewHint.visibility = View.VISIBLE - } else { - binding.emptyView.visibility = View.GONE - binding.emptyViewHint.visibility = View.GONE - } - } - adapter.onItemClick = { id -> - dnsServerViewModel.viewModelScope.launch { - val server = dnsServerViewModel.getById(id) - if (server != null) { - val newFragment = - AddServerDialogFragment(server) - newFragment.show(supportFragmentManager, "edit_server") - } - } - } - adapter.onDragStart = { viewHolder -> - itemTouchHelper.startDrag(viewHolder) - } - binding.floatingActionButton.setOnClickListener { - val newFragment = AddServerDialogFragment(null) - newFragment.show(supportFragmentManager, "add_server") - } - binding.recyclerView.adapter = adapter - itemTouchHelper.attachToRecyclerView(binding.recyclerView) - - binding.topAppBar.setOnMenuItemClickListener { item -> - when (item.itemId) { - R.id.privacy_policy -> { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse("https://karasevm.github.io/PrivateDNSAndroid/privacy_policy") - ) - startActivity(browserIntent) - true - } - - R.id.export_settings_clipboard -> { - dnsServerViewModel.viewModelScope.launch { - val data = BackupUtils.export(dnsServerViewModel, sharedPrefs) - 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( - applicationContext, getString(R.string.copy_success), Toast.LENGTH_SHORT - ).show() - } - true - } - - R.id.export_settings_share -> { - val activityContext = this - dnsServerViewModel.viewModelScope.launch { - val data = BackupUtils.export(dnsServerViewModel, sharedPrefs) - val jsonData = Json.encodeToString(data) - ShareCompat.IntentBuilder(activityContext).setText(jsonData) - .setType("text/plain") - .startChooser() - } - true - } - - R.id.export_settings_file -> { - - dnsServerViewModel.viewModelScope.launch { - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "text/plain" - putExtra(Intent.EXTRA_TITLE, "private-dns-export") - } - saveResultLauncher.launch(intent) - } - true - } - - R.id.import_settings_file -> { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "text/plain" - } - importResultLauncher.launch(intent) - true - } - - R.id.import_settings_clipboard -> { - val clipData = clipboard.primaryClip?.getItemAt(0) - val textData = clipData?.text - - if (textData != null) { - importSettings(textData.toString()) - } - true - } - - R.id.options -> { - val newFragment = OptionsDialogFragment() - newFragment.show(supportFragmentManager, "options") - true - } - - else -> true - } - } - } - - private var saveResultLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - val data: Intent? = result.data - data?.data?.also { uri -> - val jsonData = Json.encodeToString(BackupUtils.export(dnsServerViewModel, sharedPrefs)) - val contentResolver = applicationContext.contentResolver - runCatching { - contentResolver.openOutputStream(uri)?.use { outputStream -> - outputStream.write(jsonData.toByteArray()) - } - }.onFailure { exception -> - Log.e("EXPORT", "Export failed", exception) - Toast.makeText( - this, getString(R.string.export_failure), Toast.LENGTH_SHORT - ).show() - }.onSuccess { - Toast.makeText( - this, getString(R.string.export_success), Toast.LENGTH_SHORT - ).show() - } - } - } - } - - private var importResultLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - val data: Intent? = result.data - data?.data?.also { uri -> - val contentResolver = applicationContext.contentResolver - contentResolver.openInputStream(uri)?.use { inputStream -> - val jsonData = inputStream.bufferedReader().use { it.readText() } - importSettings(jsonData) - } - } - } - } - - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.menu_main, menu) - return true - } - - override fun onResume() { - super.onResume() - // Check if WRITE_SECURE_SETTINGS is granted - if (checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { - // Check if Shizuku is available - if (Shizuku.pingBinder()) { - // check if permission is granted already - val isGranted = if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) { - checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED - } else { - Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED - } - // request permission if not granted - if (!isGranted && !Shizuku.shouldShowRequestPermissionRationale()) { - if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) { - requestPermissions(arrayOf(ShizukuProvider.PERMISSION), 1) - } else { - Shizuku.requestPermission(1) - } - } else { - grantPermission() - } - } else { - if (checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { - val browserIntent = Intent( - 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() - } - } - } - } - - override fun onWindowFocusChanged(hasFocus: Boolean) { - super.onWindowFocusChanged(hasFocus) - if (!hasFocus) { - // Gets the ID of the "paste" menu item. - val pasteItem = binding.topAppBar.menu.findItem(R.id.import_settings_clipboard) - - // If the clipboard doesn't contain data, disable the paste menu item. - // If it does contain data, decide whether you can handle the data. - pasteItem.isEnabled = when { - !clipboard.hasPrimaryClip() -> false - !(clipboard.primaryClipDescription?.hasMimeType(MIMETYPE_TEXT_PLAIN))!! -> false - else -> true - - } - } - } - - override fun onDestroy() { - super.onDestroy() - Shizuku.removeRequestPermissionResultListener(this::onRequestPermissionResult) - } - - /** - * Show the dialog for deleting the server - * @param id The server id - */ - override fun onDeleteItemClicked(id: Int) { - val newFragment = DeleteServerDialogFragment(id) - newFragment.show(supportFragmentManager, "delete_server") - } - - /** - * Callback for adding the server - * @param label The label - * @param server The server - */ - override fun onAddDialogPositiveClick(label: String?, server: String) { - if (server.isEmpty()) { - Toast.makeText(this, R.string.server_length_error, Toast.LENGTH_SHORT).show() - return - } - - if (label.isNullOrEmpty()) { - dnsServerViewModel.insert(DnsServer(0, server)) - } else { - dnsServerViewModel.insert(DnsServer(0, server, label)) - } - } - - /** - * Callback for deleting the server - * @param id The server id - */ - override fun onDeleteDialogPositiveClick(id: Int) { - dnsServerViewModel.delete(id) - } - - /** - * Callback for updating the server - * @param label New label - * @param server New server address - * @param id The server id - */ - override fun onUpdateDialogPositiveClick( - id: Int, - server: String, - label: String?, - enabled: Boolean - ) { - if (server.isEmpty()) { - Toast.makeText(this, R.string.server_length_error, Toast.LENGTH_SHORT).show() - return - } - dnsServerViewModel.update(id, server, label, null, enabled) - } - - 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 && 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() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/ui/OptionsDialogFragment.kt b/app/src/main/java/ru/karasevm/privatednstoggle/ui/OptionsDialogFragment.kt deleted file mode 100644 index 2ca7b14..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/ui/OptionsDialogFragment.kt +++ /dev/null @@ -1,63 +0,0 @@ -package ru.karasevm.privatednstoggle.ui - -import android.app.Dialog -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import ru.karasevm.privatednstoggle.R -import ru.karasevm.privatednstoggle.databinding.DialogOptionsBinding -import ru.karasevm.privatednstoggle.util.PreferenceHelper -import ru.karasevm.privatednstoggle.util.PreferenceHelper.autoMode -import ru.karasevm.privatednstoggle.util.PreferenceHelper.requireUnlock -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? - ): Dialog { - return activity?.let { - val builder = MaterialAlertDialogBuilder(it) - val inflater = requireActivity().layoutInflater - _binding = DialogOptionsBinding.inflate(inflater) - - val view = binding.root - builder.setTitle(R.string.options) - .setView(view) - .setPositiveButton(R.string.ok, null) - builder.create() - - } ?: throw IllegalStateException("Activity cannot be null") - } - - override fun onStart() { - super.onStart() - 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) - PrivateDNSUtils.AUTO_MODE_OPTION_OFF_AUTO -> binding.autoOptionRadioGroup.check(R.id.autoOptionOffAuto) - PrivateDNSUtils.AUTO_MODE_OPTION_PRIVATE -> binding.autoOptionRadioGroup.check(R.id.autoOptionPrivate) - } - binding.autoOptionRadioGroup.setOnCheckedChangeListener { _, checkedId -> - when (checkedId) { - 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 -> sharedPreferences.autoMode = - PrivateDNSUtils.AUTO_MODE_OPTION_PRIVATE - } - } - - val requireUnlock = sharedPreferences.requireUnlock - binding.requireUnlockSwitch.isChecked = requireUnlock - binding.requireUnlockSwitch.setOnCheckedChangeListener { _, isChecked -> - sharedPreferences.requireUnlock = isChecked - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/ui/ServerListRecyclerAdapter.kt b/app/src/main/java/ru/karasevm/privatednstoggle/ui/ServerListRecyclerAdapter.kt deleted file mode 100644 index fec1eb0..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/ui/ServerListRecyclerAdapter.kt +++ /dev/null @@ -1,124 +0,0 @@ -package ru.karasevm.privatednstoggle.ui - -import android.annotation.SuppressLint -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import ru.karasevm.privatednstoggle.R -import ru.karasevm.privatednstoggle.model.DnsServer - - -class ServerListRecyclerAdapter(private val showDragHandle: Boolean) : - RecyclerView.Adapter() { - - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DnsServerViewHolder { - val view = - LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_row, parent, false) - val vh = DnsServerViewHolder(view) - return vh - } - - override fun getItemCount(): Int { - return items.size - } - - var onItemClick: ((Int) -> Unit)? = null - var onDragStart: ((DnsServerViewHolder) -> Unit)? = null - private var items: MutableList = mutableListOf() - - @SuppressLint("ClickableViewAccessibility") - override fun onBindViewHolder(holder: DnsServerViewHolder, position: Int) { - val item = items[position] - if (item.label.isNotEmpty()) { - holder.labelTextView.text = item.label - holder.labelTextView.visibility = View.VISIBLE - } else { - holder.labelTextView.visibility = View.GONE - } - holder.serverTextView.text = item.server - holder.id = item.id - if (item.enabled) { - holder.labelTextView.alpha = 1f - holder.serverTextView.alpha = 1f - } else { - holder.labelTextView.alpha = 0.5f - holder.serverTextView.alpha = 0.5f - } - if (showDragHandle) { - holder.dragHandle.visibility = View.VISIBLE - holder.dragHandle.setOnTouchListener { _, event -> - if (event.actionMasked == MotionEvent.ACTION_DOWN) { - onDragStart?.invoke(holder) - } - return@setOnTouchListener true - } - } - } - - /** - * Update server position in memory - * @param fromPosition old position - * @param toPosition new position - */ - fun onItemMove(fromPosition: Int, toPosition: Int) { - items.add(toPosition, items.removeAt(fromPosition)) - notifyItemMoved(fromPosition, toPosition) - } - - class DiffCallback( - private val oldList: List, private var newList: List - ) : DiffUtil.Callback() { - - override fun getOldListSize(): Int { - return oldList.size - } - - override fun getNewListSize(): Int { - return newList.size - } - - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - val oldItem = oldList[oldItemPosition] - val newItem = newList[newItemPosition] - return oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - val oldItem = oldList[oldItemPosition] - val newItem = newList[newItemPosition] - return oldItem.server == newItem.server && oldItem.label == newItem.label && oldItem.enabled == newItem.enabled - } - } - - /** - * Submit list to adapter - * @param list list to submit - */ - fun submitList(list: List) { - val diffCallback = DiffCallback(items, list) - val diffResult = DiffUtil.calculateDiff(diffCallback) - items.clear() - items.addAll(list) - diffResult.dispatchUpdatesTo(this) - } - - inner class DnsServerViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val labelTextView: TextView = view.findViewById(R.id.labelTextView) - val serverTextView: TextView = view.findViewById(R.id.textView) - val dragHandle: ImageView = itemView.findViewById(R.id.dragHandle) - var id = 0 - - init { - view.setOnClickListener { - onItemClick?.invoke(id) - } - } - } - -} diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/ui/SettingsDialogActivity.kt b/app/src/main/java/ru/karasevm/privatednstoggle/ui/SettingsDialogActivity.kt deleted file mode 100644 index 4b15e05..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/ui/SettingsDialogActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ru.karasevm.privatednstoggle.ui - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class SettingsDialogActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val newFragment = DNSServerDialogFragment() - newFragment.show(supportFragmentManager, DNSServerDialogFragment.TAG) - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/util/BackupUtils.kt b/app/src/main/java/ru/karasevm/privatednstoggle/util/BackupUtils.kt deleted file mode 100644 index 304f1db..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/util/BackupUtils.kt +++ /dev/null @@ -1,79 +0,0 @@ -package ru.karasevm.privatednstoggle.util - -import android.content.SharedPreferences -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.autoMode -import ru.karasevm.privatednstoggle.util.PreferenceHelper.requireUnlock - -object BackupUtils { - @Serializable - data class Backup( - @SerialName("dns_servers") val dnsServers: List, - @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?, - ) - - /** - * Exports all the preferences - * @param viewModel View model - * @param sharedPreferences Shared preferences - */ - fun export(viewModel: DnsServerViewModel, sharedPreferences: SharedPreferences): Backup { - return Backup( - viewModel.allServers.value ?: listOf(), - sharedPreferences.autoMode, - sharedPreferences.requireUnlock - ) - } - - /** - * Imports all the preferences - * @param backup Deserialized backup - * @param viewModel View model - */ - fun import( - backup: Backup, - viewModel: DnsServerViewModel, - sharedPreferences: SharedPreferences - ) { - backup.dnsServers.forEach { viewModel.insert(it) } - sharedPreferences.autoMode = backup.autoMode ?: sharedPreferences.autoMode - sharedPreferences.requireUnlock = backup.requireUnlock ?: sharedPreferences.requireUnlock - } - - /** - * Imports old server list - * @param legacyBackup Deserialized backup - * @param viewModel View model - * @param sharedPreferences Shared preferences - */ - fun importLegacy( - legacyBackup: LegacyBackup, - viewModel: DnsServerViewModel, - sharedPreferences: SharedPreferences - ) { - legacyBackup.dnsServers.let { servers -> - val serverList = servers.split(",") - serverList.forEach { server -> - val parts = server.split(" : ") - if (parts.size == 2) { - viewModel.insert(DnsServer(0, parts[1], parts[0])) - } else { - viewModel.insert(DnsServer(0, server, "")) - } - } - } - sharedPreferences.autoMode = legacyBackup.autoMode?: 0 - sharedPreferences.requireUnlock = legacyBackup.requireUnlock == true - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/util/PrivateDNSUtils.kt b/app/src/main/java/ru/karasevm/privatednstoggle/util/PrivateDNSUtils.kt deleted file mode 100644 index 9441421..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/util/PrivateDNSUtils.kt +++ /dev/null @@ -1,52 +0,0 @@ -package ru.karasevm.privatednstoggle.util - -import android.Manifest -import android.content.ContentResolver -import android.content.Context -import android.content.pm.PackageManager -import android.provider.Settings -import androidx.core.content.ContextCompat.checkSelfPermission - -@Suppress("unused") -object PrivateDNSUtils { - const val DNS_MODE_OFF = "off" - const val DNS_MODE_AUTO = "opportunistic" - const val DNS_MODE_PRIVATE = "hostname" - - const val AUTO_MODE_OPTION_OFF = 0 - const val AUTO_MODE_OPTION_AUTO = 1 - const val AUTO_MODE_OPTION_OFF_AUTO = 2 - const val AUTO_MODE_OPTION_PRIVATE = 3 - - private const val PRIVATE_DNS_MODE = "private_dns_mode" - private const val PRIVATE_DNS_PROVIDER = "private_dns_specifier" - - - // Gets the system dns mode - fun getPrivateMode(contentResolver: ContentResolver): String { - return Settings.Global.getString(contentResolver, PRIVATE_DNS_MODE) - } - - // Gets the system dns provider - fun getPrivateProvider(contentResolver: ContentResolver): String { - return Settings.Global.getString(contentResolver, PRIVATE_DNS_PROVIDER) - } - - // Sets the system dns mode - fun setPrivateMode(contentResolver: ContentResolver, value: String) { - Settings.Global.putString(contentResolver, PRIVATE_DNS_MODE, value) - } - - // Sets the system dns provider - fun setPrivateProvider(contentResolver: ContentResolver, value: String?) { - Settings.Global.putString(contentResolver, PRIVATE_DNS_PROVIDER, value) - } - - fun checkForPermission(context: Context): Boolean { - return checkSelfPermission( - context, - Manifest.permission.WRITE_SECURE_SETTINGS - ) == PackageManager.PERMISSION_GRANTED - } - -} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/util/SharedPrefUtils.kt b/app/src/main/java/ru/karasevm/privatednstoggle/util/SharedPrefUtils.kt deleted file mode 100644 index 129a56b..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/util/SharedPrefUtils.kt +++ /dev/null @@ -1,57 +0,0 @@ -package ru.karasevm.privatednstoggle.util - -import android.content.Context -import android.content.SharedPreferences - -object PreferenceHelper { - - const val DNS_SERVERS = "dns_servers" - const val AUTO_MODE = "auto_mode" - const val REQUIRE_UNLOCK = "require_unlock" - - fun defaultPreference(context: Context): SharedPreferences = - context.getSharedPreferences("app_prefs", 0) - - private inline fun SharedPreferences.editMe(operation: (SharedPreferences.Editor) -> Unit) { - val editMe = edit() - operation(editMe) - editMe.apply() - } - - private fun SharedPreferences.Editor.put(pair: Pair) { - val key = pair.first - when (val value = pair.second) { - is String -> putString(key, value) - is Int -> putInt(key, value) - is Boolean -> putBoolean(key, value) - is Long -> putLong(key, value) - is Float -> putFloat(key, value) - else -> error("Only primitive types can be stored in SharedPreferences, got ${value.javaClass}") - } - } - - var SharedPreferences.dns_servers - get() = getString(DNS_SERVERS, "")!!.split(",").toMutableList() - set(items) { - editMe { - it.put(DNS_SERVERS to items.joinToString(separator = ",")) - } - } - - - var SharedPreferences.autoMode - get() = getInt(AUTO_MODE, PrivateDNSUtils.AUTO_MODE_OPTION_OFF) - set(value) { - editMe { - it.put(AUTO_MODE to value) - } - } - - var SharedPreferences.requireUnlock - get() = getBoolean(REQUIRE_UNLOCK, false) - set(value) { - editMe { - it.put(REQUIRE_UNLOCK to value) - } - } -} diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/util/ShizukuUtil.kt b/app/src/main/java/ru/karasevm/privatednstoggle/util/ShizukuUtil.kt deleted file mode 100644 index fe9b5cb..0000000 --- a/app/src/main/java/ru/karasevm/privatednstoggle/util/ShizukuUtil.kt +++ /dev/null @@ -1,82 +0,0 @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable-anydpi/ic_tile_default.xml b/app/src/main/res/drawable-anydpi/ic_tile_default.xml new file mode 100644 index 0000000..2af57be --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_tile_default.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_tile_default.png b/app/src/main/res/drawable-hdpi/ic_tile_default.png new file mode 100644 index 0000000..9809f36 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_tile_default.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_tile_default.png b/app/src/main/res/drawable-mdpi/ic_tile_default.png new file mode 100644 index 0000000..cb09290 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_tile_default.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_tile_default.png b/app/src/main/res/drawable-xhdpi/ic_tile_default.png new file mode 100644 index 0000000..5564007 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_tile_default.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_tile_default.png b/app/src/main/res/drawable-xxhdpi/ic_tile_default.png new file mode 100644 index 0000000..35f5c5f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_tile_default.png differ diff --git a/app/src/main/res/drawable/ic_auto_black_24dp.xml b/app/src/main/res/drawable/ic_auto_black_24dp.xml index d3abbcd..3ddd864 100644 --- a/app/src/main/res/drawable/ic_auto_black_24dp.xml +++ b/app/src/main/res/drawable/ic_auto_black_24dp.xml @@ -4,10 +4,10 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml index a904f51..70046c4 100644 --- a/app/src/main/res/drawable/ic_baseline_add_24.xml +++ b/app/src/main/res/drawable/ic_baseline_add_24.xml @@ -1,5 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_settings_24.xml b/app/src/main/res/drawable/ic_baseline_settings_24.xml deleted file mode 100644 index e7e3d4f..0000000 --- a/app/src/main/res/drawable/ic_baseline_settings_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_drag_handle_24.xml b/app/src/main/res/drawable/ic_drag_handle_24.xml deleted file mode 100644 index 892b734..0000000 --- a/app/src/main/res/drawable/ic_drag_handle_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_unknown_black_24dp.xml b/app/src/main/res/drawable/ic_unknown_black_24dp.xml deleted file mode 100644 index 3ddd864..0000000 --- a/app/src/main/res/drawable/ic_unknown_black_24dp.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 88e1536..0ff5544 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,65 +4,12 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.MainActivity" - android:fitsSystemWindows="true"> - - - - - - + tools:context=".MainActivity"> - - - - - - + android:layout_height="match_parent" + tools:layout_editor_absoluteX="1dp" + tools:layout_editor_absoluteY="1dp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_add.xml b/app/src/main/res/layout/dialog_add.xml index fe08fe9..1ec3b51 100644 --- a/app/src/main/res/layout/dialog_add.xml +++ b/app/src/main/res/layout/dialog_add.xml @@ -4,60 +4,18 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - - - - - - - - - - - - + app:layout_constraintTop_toTopOf="parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_options.xml b/app/src/main/res/layout/dialog_options.xml deleted file mode 100644 index 11f6ce1..0000000 --- a/app/src/main/res/layout/dialog_options.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/recyclerview_row.xml b/app/src/main/res/layout/recyclerview_row.xml index 4da379a..fa7bb16 100644 --- a/app/src/main/res/layout/recyclerview_row.xml +++ b/app/src/main/res/layout/recyclerview_row.xml @@ -1,45 +1,22 @@ + android:focusable="true"> - - - - - + app:layout_constraintTop_toTopOf="parent" + /> \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_dns_selector.xml b/app/src/main/res/layout/sheet_dns_selector.xml deleted file mode 100644 index bbe5b42..0000000 --- a/app/src/main/res/layout/sheet_dns_selector.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index fcfec62..49e94e8 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -1,40 +1,11 @@ - - - - - - - - - - - - - - + xmlns:android="http://schemas.android.com/apk/res/android" > + \ No newline at end of file diff --git a/app/src/main/res/mipmap/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 70% rename from app/src/main/res/mipmap/ic_launcher.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 5c84730..7353dbd 100644 --- a/app/src/main/res/mipmap/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,5 @@ - - - - + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..05b82a8 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..cdd2b1b Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..b0a456c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..f61ced5 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..4f1fa21 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..7d80cf5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..89f96ba Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..bdb278e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..1cd6644 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..2c7bdb7 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties deleted file mode 100644 index d5a3ddc..0000000 --- a/app/src/main/res/resources.properties +++ /dev/null @@ -1 +0,0 @@ -unqualifiedResLocale=en-US \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml deleted file mode 100644 index 048dcdf..0000000 --- a/app/src/main/res/values-fr/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Terminé - Private DNS Quick Toggle - Inconnu - Éteint - Ajouter un serveur - Ajouter - Politique de confidentialité - Êtes-vous sûr de vouloir supprimer le serveur ? - Supprimer - L\'adresse du serveur ne peut pas être vide - Étiquette du serveur DNS (Facultatif) - Adresse du serveur DNS - Options - OK - Seulement éteint - Seulement automatique - Éteint et automatique - Automatique - Commutateur de DNS privé - Enregistrer - Autorisation non accordée, vérifiez les instructions dans l\'application - Sélectionner le serveur - Définissez les options à inclure - DNS privé seulement - Ouvrir l\'application - DNS privé éteint - DNS privé réglé sur automatique - DNS privé réglé sur %1$s - Requiert le déverrouillage de l\'appareil pour changer de serveur - Activé - À partir du fichier - Aucun serveur ajouté - Copié - Éditer le serveur - Échec de la sauvegarde - Appuyez sur le bouton ci-dessous pour en ajouter un - Impossible d\'obtenir l\'autorisation, veuillez l\'accorder manuellement - Autorisation accordée, vous pouvez désormais révoquer l\'autorisation Shizuku - À partir du presse-papier - Vers le presse-papier - Partager - Vers le fichier - Sauvegarde réussie - Échec de l\'importation - Échec de l\'importation, le fichier JSON est incorrect - Importé - Exporter - Importer - Poignée - Supprimer - Annuler - diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml deleted file mode 100644 index 3e9260d..0000000 --- a/app/src/main/res/values-hu/strings.xml +++ /dev/null @@ -1,51 +0,0 @@ - - Privát DNS Gyorskapcsoló - Privát DNS Kapcsoló - Nincs engedély megadva, nézd meg az alkalmazásban, hogyan adhatod meg - Ki - Automatikus - Ismeretlen - Szerver hozzáadása - Hozzáadás - Mentés - Adatvédelmi irányelvek - Szerver kiválasztása - Kész - Mégse - Törlés - Biztosan törölni szeretnéd a szervert? - Törlés - A szervercím nem lehet üres - DNS szerver neve (opcionális) - DNS szerver címe - Beállítások - OK - Válaszd ki, mely opciók jelenjenek meg a csempén - Csak ki - Csak automatikus - Ki és automatikus - Csak Privát DNS - Alkalmazás megnyitása - Privát DNS kikapcsolva - Privát DNS automatikus módra állítva - Privát DNS beállítva: %1$s - Eszköz feloldása szükséges a szerver módosításához - Húzási fogantyú - Importálás - Exportálás - Importálva - Importálás sikertelen - Importálás sikertelen, hibás JSON - Másolva - Fájlból - Vágólapról - Vágólapra - Megosztás - Fájlba - Mentés sikertelen - Sikeresen mentve - Szerver szerkesztése - Nincsenek szerverek hozzáadva - Koppints az alábbi gombra, hogy hozzáadj egyet - Engedélyezve - diff --git a/app/src/main/res/values-mn/strings.xml b/app/src/main/res/values-mn/strings.xml deleted file mode 100644 index 10de470..0000000 --- a/app/src/main/res/values-mn/strings.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - Хувийн DNS солих - Унтраах - Тодорхойгүй - Сервер нэмэх - Хадгалах - Болсон - Болих - Устгах - Та серверийг устгахдаа итгэлтэй байна уу? - Устгах - Серверийн хаяг хоосон байж болохгүй - DNS серверийн шошго (заавал биш) - DNS серверийн хаяг - Сонголтууд - ОК - Хавтан дээр ямар сонголтыг оруулахаа сонгоно уу - Зөвхөн унтарсан - Зөвхөн авто - Унтарсан болон авто - Хувийн DNS-г %1$s болгож тохируулсан - Серверийг өөрчлөхийн тулд төхөөрөмжийн түгжээг тайлах шаардлагатай - Бариулыг чирэх - Импорт - Импортолсон - Импорт хийж чадсангүй - Файлаас - Хадгалж чадсангүй - Амжилттай хадгалсан - Сервер засах - Хувийн DNS хурдан сэлгэх - Нууцлалын бодлого - Зөвшөөрөл олгоогүй. Үүнийг хэрхэн хийхийг харна уу - Авто - Нэмэх - Сервер сонгох - Зөвхөн хувийн DNS - Апп нээх - Түр санах ой руу - Хувийн DNS унтарсан - Хувийн DNS-г автоматаар тохируулсан - Экспорт - Хуулагдсан - Хуваалцах - Импорт хийж чадсангүй, алдаатай JSON - Түр санах ойноос - Файлруу - \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 715cab4..203e219 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,12 +1,16 @@ - - \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml deleted file mode 100644 index 6c76614..0000000 --- a/app/src/main/res/values-pl/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Usuń - Zapisz - Polityka prywatności - Wybierz serwer - Anuluj - Adres serwera DNS - OK - Otwórz aplikację - Importuj - Eksportuj - Skopiowano - Z pliku - Ze schowka - Do schowka - Udostępnij - Do pliku - Edytuj serwer - Brak dodanych serwerów - Włączone - Dodaj serwer - Dodaj - Usuń - Nieznane - Gotowe - Opcje - Importowanie nie powiodło się - Automatycznie - Wyłącz - Zaimportowano - Adres serwera nie może być pusty - Import nie powiódł się, zniekształcony plik JSON - Zapisano pomyślnie - Czy na pewno chcesz usunąć serwer? - Private DNS Quick Toggle - Przełącznik prywatnego DNS - Nieprzydzielono uprawnienia, sprawdź w aplikacji, w jaki sposób można to zrobić - Opis serwera DNS (opcjonalnie) - Wybierz opcje, które będą dostępne w kafelku - Tylko wyłączenie - Tylko automatycznie - Wyłączenie i automatycznie - Tylko prywatny DNS - Prywatny DNS zmieniony na automatyczny - Prywatny DNS zmieniony na %1$s - Wymagaj odblokowania urządzenia do zmiany serwera - Wyłączono Prywatny DNS - Przeciągnij - Zapisywanie nie powiodło się - Kliknij na poniższy przycisk, aby dodać nowy - Udzielono zezwolenia, możesz teraz cofnąć zezwolenie w Shizuku - Uzyskanie uprawnień nie powiodło się, udziel ich ręcznie - diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml deleted file mode 100644 index c4691bd..0000000 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Importado - Permissão não concedida, verifique o app para saber como prosseguir - Alteração de DNS privado - Cancelar - Adicionar servidor - Endereço do servidor DNS - Escolha opções disponível em atalho - Alteração de DNS privado - Desativado - Automático - Indeterminado - Adicionar - Salvar - Política de privacidade - Concluído - Apagar - Tem certeza de que quer apagar o servidor? - Apagar - O endereço do servidor não pode estar em branco - Identificação do servidor DNS (opcional) - Opções - Ok - Somente desativado - Desativado e automático - Somente DNS privado - Abrir app - DNS privado desativado - DNS privado definido para automático - DNS privado definido para %1$s - Arrastre - Importar - Exportar - Falha na importação, JSON malformado - Copiado - Da memória - Compartilhar - Para arquivo - Salvo com sucesso - Editar servidor - Nenhum servidor adicionado - Toque no botão abaixo para adicionar - Ativado - Escolha servidor - Para memória - De arquivo - Falha ao importar - Falha ao salvar - Necessário desbloquear o dispositivo para alterar servidor - Somente automático - Falha ao obter a permissão. Tente conceder manualmente - Permissão concedida, você pode revogar a permissão do Shizuku agora - diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml deleted file mode 100644 index cef59e3..0000000 --- a/app/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,53 +0,0 @@ - - Private DNS Quick Toggle - Переключить частный DNS - Разрешение не предоставлено, проверьте приложение для получения информации - Выкл - Авто - Неизвестно - Добавить сервер - Добавить - Сохранить - Политика конфиденциальности - Выбрать сервер - Готово - Отмена - Удалить - Вы уверены, что хотите удалить сервер? - Удалить - Адрес сервера не может быть пустым - Название DNS сервера (необязательно) - Адрес DNS сервера - Опции - OK - Выберите, какие опции включить в плитке - Только \"Выкл\" - Только \"Авто\" - \"Выкл\" и \"Авто\" - Только частный DNS - Открыть приложение - Частный DNS выключен - Частный DNS установлен на "Авто" - Частный DNS установлен на %1$s - Смена сервера требует разблокировки устройства - Ручка перетаскивания - Импорт - Экспорт - Успешно импортировано - Импорт не удался - Импорт не удался, некорректный JSON - Скопировано - Из файла - Из буфера обмена - В буфер обмена - Поделиться - В файл - Сохранение не удалось - Успешно сохранено - Редактировать сервер - Нет доступных серверов - Нажмите на кнопку ниже, чтобы добавить сервер - Включён - Разрешение получено, можно отозвать авторизацию Shizuku - Не удалось получить разрешение, предоставьте его вручную - \ No newline at end of file diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml deleted file mode 100644 index 1cb02d2..0000000 --- a/app/src/main/res/values-ta/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - சரி - தனியார் டி.என்.எச் விரைவாக மாற்று - தனியார் டி.என் கள் மாறுகின்றன - இசைவு வழங்கப்படவில்லை, அதை எப்படி செய்வது என்று பார்க்க பயன்பாட்டை சரிபார்க்கவும் - அணை - தானி - தெரியவில்லை - சேவையகத்தைச் சேர்க்கவும் - கூட்டு - சேமி - தனியுரிமைக் கொள்கை - சேவையகத்தைத் தேர்ந்தெடுக்கவும் - முடிந்தது - ரத்துசெய் - நீக்கு - சேவையகத்தை நீக்க விரும்புகிறீர்களா? - நீக்கு - டிஎன்எச் சேவையக முகவரி - விருப்பங்கள் - ஓடுகளில் எந்த விருப்பங்களைச் சேர்க்க வேண்டும் என்பதைத் தேர்வுசெய்க - மட்டுமே - ஆட்டோ மட்டுமே - ஆஃப் மற்றும் ஆட்டோ - தனியார் டி.என்.எச் மட்டுமே - திறந்த பயன்பாடு - தனியார் டி.என்.எச் அணைக்கப்பட்டது - தனியார் டி.என்.எச் ஆட்டோவாக அமைக்கப்பட்டுள்ளது - தனியார் டி.என்.எச் %1$s என அமைக்கப்பட்டுள்ளது - சேவையகத்தை மாற்ற சாதனத்தைத் திறக்க வேண்டும் - இழுவை கைப்பிடி - இறக்குமதி - ஏற்றுமதி - இறக்குமதி செய்யப்பட்டது - இறக்குமதி தோல்வியடைந்தது - இறக்குமதி தோல்வியுற்றது, தவறாக சாதொபொகு - நகலெடுக்கப்பட்டது - கோப்பிலிருந்து - கிளிப்போர்டிலிருந்து - இடைநிலைப்பலகைக்கு - பங்கு - தாக்கல் செய்ய - சேமிப்பு தோல்வியடைந்தது - வெற்றிகரமாக சேமிக்கப்பட்டது - சேவையகத்தைத் திருத்து - சேவையகங்கள் எதுவும் சேர்க்கப்படவில்லை - ஒன்றைச் சேர்க்க கீழே உள்ள பொத்தானைத் தட்டவும் - இயக்கப்பட்டது - இசைவு வழங்கப்பட்டது, நீங்கள் இப்போது சிசுகு அனுமதியை ரத்து செய்யலாம் - இசைவு பெறுவதில் தோல்வி, தயவுசெய்து அதை கைமுறையாக வழங்கவும் - சேவையக முகவரி காலியாக இருக்க முடியாது - டிஎன்எச் சேவையக சிட்டை (விரும்பினால்) - diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml deleted file mode 100644 index 57f2c8d..0000000 --- a/app/src/main/res/values-tr/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Sunucuyu silmek istediğinizden emin misiniz? - Sadece kapalı - Sunucuyu değiştirmek için cihazın kilidini açmanız gerekiyor - Oto - Bilinmeyen - Tamam - İptal - DNS sunucusu Etiketi (İsteğe bağlı) - Seçenekler - OK - Sadece oto - Kapalı ve oto - Özel DNS kapandı - Uygulamayı açınız - İçe aktar - Dışa aktar - içe aktarıldı - Dosyaya - Kaydetme başarısız oldu - Hiç Sunucu Eklenmedi - Eklemek için aşağıdaki düğmeye dokunun - İzin alınamadı, lütfen manuel olarak verin - Karoya hangi seçeneklerin dahil edileceğini seçiniz - Kapalı - Gizlilik Politikası - Sil - Sunucu adresi boş olamaz - Sil - DNS sunucu adresi - tutacağı sürükle - Ekle - Sunucu Ekleyiniz - İzin verilmedi, nasıl yapıldığını görmek için uygulamayı kontrol ediniz - İçe aktarma başarısız oldu, hatalı biçimlendirilmiş JSON - Özel DNS Hızlı Geçiş - İzin verildi, şimdi Shizuku iznini iptal edebilirsiniz - Aktarma başarısız - Panodan - Başarıyla kaydedildi - Kaydet - Özel DNS otomatik olarak ayarlandı - Özel DNS %1$s olarak ayarlandı - Sadece Özel DNS - Sunucuyu Seçin - Kopyalandı - Sunucuyu düzenle - Etkin - Özel DNS Geçişi - Dosyadan - Panoya - Paylaş - diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml deleted file mode 100644 index 1f423ae..0000000 --- a/app/src/main/res/values-vi/strings.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - Thêm máy chủ - Chỉ tự động - Chưa được cấp quyền, hãy kiểm tra ứng dụng để biết cách thực hiện - Tắt - Tự động - Thêm - Chính sách bảo mật - Chọn máy chủ - Hoàn thành - Hủy - Bạn có chắc chắn muốn xóa máy chủ không? - Xoá - Địa chỉ máy chủ DNS - Tùy chọn - OK - Chọn các tùy chọn để đưa vào ô - Chỉ tắt - Tắt và tự động - Mở ứng dụng - DNS cá nhân được thiết lập tự động - Yêu cầu mở khóa thiết bị để thay đổi máy chủ - Tay cầm kéo - Xuất - Đã nhập - Nhập thất bại - Nhập thất bại, JSON bị lỗi - Đã sao chép - Từ tập tin - Chia sẻ - Thành tập tin - Lưu không thành công - Chỉnh sửa máy chủ - Chưa có máy chủ nào - Nhấn vào nút bên dưới để thêm - Đã bật - Chuyển đổi DNS cá nhân - Xoá - Chuyển đổi nhanh DNS cá nhân - Không rõ - Nhãn máy chủ DNS (Không bắt buộc) - Lưu - Địa chỉ máy chủ không được để trống - Chỉ DNS cá nhân - Đã tắt DNS cá nhân - DNS cá nhân được đặt thành %1$s - Nhập - Từ bảng nhớ tạm - Vào bảng nhớ tạm - Đã lưu thành công - \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml deleted file mode 100644 index a217315..0000000 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - 私有DNS触发 - 必要权限未授予,请看相关说明 - 关闭 - 自动 - 未知 - 添加DNS服务器 - 添加 - 存储 - 隐私策略 - 选择服务器 - 完成 - 取消 - 删除条目 - 你确认要删除这个服务器条目吗? - 删除 - 服务器地址不可为空 - DNS服务器标识 - DNS服务器地址 - 选项 - 确认 - 选择要在磁贴中启用的选项 - 仅“关闭” - 仅“自动” - “关闭“与”自动“ - 仅设置的私有DNS - 打开软件 - 不使用私有DNS - 自动使用私有DNS - 设置为使用私有DNS\"%1$s\" - 更改服务器设置要求设备解锁 - 拖动把手 - 导入 - 导出 - 已导入 - 导入失败 - 导入失败,json格式异常 - 已复制 - 从文件导入 - 从剪贴板导入 - 导出至剪贴板 - 分享 - 导出至文件 - 保存失败 - 保存成功 - 编辑服务器条目 - 无可用服务器 - 点击下方\"+\"添加一个吧 - 已启用 - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3643757..f8c6127 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,15 +1,10 @@ - - - #498EE8 - - #6750A4 - #6750A4 - #625B71 - #7D5260 - #FFFBFE - - #D0BCFF - #CCC2DC - #EFB8C8 - #1C1B1F - + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..cf31181 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #498EE8 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8124a26..462dcc2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,50 +4,13 @@ Permission not granted, check app to see how to do it Off Auto - Unknown Add Server - Add - Save - Privacy Policy - Select Server - Done + Add Cancel - Delete - Are you sure you want to delete server? + Delete server? Delete Server address cannot be empty - DNS server Label (Optional) + Privacy Policy DNS server address - Options - OK - Choose which options to include in the tile - Only off - Only auto - Off and auto - Only Private DNS - Open app - Private DNS turned off - Private DNS set to auto - Private DNS set to %1$s - Require unlocking the device to change server - Drag handle - Import - Export - Imported - Import failed - Import failed, malformed JSON - Copied - From file - From clipboard - To clipboard - Share - To file - Saving failed - Saved successfully - Edit server - No Servers Added - Tap on the button below to add one - Enabled - Permission granted, you can revoke the Shizuku permission now - Failed to acquire permission, please grant it manually + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index ea05d60..eebc2d0 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,27 +1,16 @@ - - - - \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d1dcc18 --- /dev/null +++ b/build.gradle @@ -0,0 +1,18 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.3.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 55f49e3..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,19 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath("com.android.tools.build:gradle:8.11.0") - 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 "2.2.0" apply false - id("org.jetbrains.kotlin.plugin.serialization") version "2.2.0" - id("com.google.devtools.ksp") version "2.2.0-2.0.2" apply false -} diff --git a/fastlane/metadata/android/en-US/changelogs/11.txt b/fastlane/metadata/android/en-US/changelogs/11.txt deleted file mode 100644 index e7b902d..0000000 --- a/fastlane/metadata/android/en-US/changelogs/11.txt +++ /dev/null @@ -1,2 +0,0 @@ -- Add option to require unlocking the device to use the tile -- Fix invisible nav buttons on some devices \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/12.txt b/fastlane/metadata/android/en-US/changelogs/12.txt deleted file mode 100644 index 46fe2d8..0000000 --- a/fastlane/metadata/android/en-US/changelogs/12.txt +++ /dev/null @@ -1,4 +0,0 @@ -- Support for selection of only Private DNS in Option Dialog by @InfiniteCoder06 -- Support Labels by @InfiniteCoder06 -- Feature: Reordring by @InfiniteCoder06 -- Possible tile update fix \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/13.txt b/fastlane/metadata/android/en-US/changelogs/13.txt deleted file mode 100644 index c546656..0000000 --- a/fastlane/metadata/android/en-US/changelogs/13.txt +++ /dev/null @@ -1,2 +0,0 @@ -- Settings export/import -- Fix label not appearing in some cases \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/14.txt b/fastlane/metadata/android/en-US/changelogs/14.txt deleted file mode 100644 index d8ece24..0000000 --- a/fastlane/metadata/android/en-US/changelogs/14.txt +++ /dev/null @@ -1,3 +0,0 @@ -- Add an option to edit servers -- Add placeholder for empty server list -- Fix layout for longer server addresses diff --git a/fastlane/metadata/android/en-US/changelogs/15.txt b/fastlane/metadata/android/en-US/changelogs/15.txt deleted file mode 100644 index 8541dd6..0000000 --- a/fastlane/metadata/android/en-US/changelogs/15.txt +++ /dev/null @@ -1,2 +0,0 @@ -- Fix crashes on Android 11 and earlier -- Fix list entry layout diff --git a/fastlane/metadata/android/en-US/changelogs/16.txt b/fastlane/metadata/android/en-US/changelogs/16.txt deleted file mode 100644 index 1005cb7..0000000 --- a/fastlane/metadata/android/en-US/changelogs/16.txt +++ /dev/null @@ -1,7 +0,0 @@ -- 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 diff --git a/fastlane/metadata/android/en-US/changelogs/17.txt b/fastlane/metadata/android/en-US/changelogs/17.txt deleted file mode 100644 index 3709a99..0000000 --- a/fastlane/metadata/android/en-US/changelogs/17.txt +++ /dev/null @@ -1,11 +0,0 @@ -- 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 diff --git a/fastlane/metadata/android/en-US/changelogs/18.txt b/fastlane/metadata/android/en-US/changelogs/18.txt deleted file mode 100644 index 067cc8f..0000000 --- a/fastlane/metadata/android/en-US/changelogs/18.txt +++ /dev/null @@ -1,12 +0,0 @@ -- 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)) diff --git a/fastlane/metadata/android/en-US/changelogs/default.txt b/fastlane/metadata/android/en-US/changelogs/default.txt deleted file mode 100644 index 300e44e..0000000 --- a/fastlane/metadata/android/en-US/changelogs/default.txt +++ /dev/null @@ -1,2 +0,0 @@ -Changelog for latest release is available on GitHub: -https://github.com/karasevm/PrivateDNSAndroid/releases/latest \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt deleted file mode 100644 index 0f03cca..0000000 --- a/fastlane/metadata/android/en-US/full_description.txt +++ /dev/null @@ -1,15 +0,0 @@ -

- Private DNS Quick Toggle is a quick settings tile to switch your private - dns provider. - Supports any number of providers. Makes it easy to turn ad-blocking - dns servers on or off with just a single tap. -

- -Permissions -

- Requires WRITE_SECURE_SETTINGS permission to change the private dns settings. - The permission must be provided either with Shizuku or - - manually through adb - . -

\ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png deleted file mode 100644 index 7f6b8f6..0000000 Binary files a/fastlane/metadata/android/en-US/images/icon.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png deleted file mode 100644 index 8d0d479..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png deleted file mode 100644 index e66eb7d..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png deleted file mode 100644 index 7b78a4a..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png deleted file mode 100644 index bc04e45..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png deleted file mode 100644 index abd46d3..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt deleted file mode 100644 index c5a798a..0000000 --- a/fastlane/metadata/android/en-US/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Quick settings tile to switch active private DNS server \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt deleted file mode 100644 index e82d84b..0000000 --- a/fastlane/metadata/android/en-US/title.txt +++ /dev/null @@ -1 +0,0 @@ -Private DNS Quick Toggle \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index e6f2676..98bed16 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,9 +16,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX -android.enableJetifier=false +android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official -android.nonTransitiveRClass=true -android.nonFinalResIds=true -org.gradle.configuration-cache=true \ No newline at end of file +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 28513c1..8d1a8cf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Aug 16 15:36:35 MSK 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/readme.jpg b/readme.jpg deleted file mode 100644 index 2119cd5..0000000 Binary files a/readme.jpg and /dev/null differ diff --git a/readme.png b/readme.png new file mode 100644 index 0000000..4dee806 Binary files /dev/null and b/readme.png differ diff --git a/settings.gradle.kts b/settings.gradle similarity index 70% rename from settings.gradle.kts rename to settings.gradle index 72cb6bf..9e0b6d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle @@ -3,7 +3,8 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + jcenter() // Warning: this repository is going to shut down soon } } rootProject.name = "Private DNS Quick Toggle" -include("app") +include ':app'