diff --git a/.gitignore b/.gitignore
index 8c470b3..8d9ff13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,3 +67,8 @@ fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
+
+# kotlin
+.kotlin/
+
+*~
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b589d56..b86273d 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
index 9a55c2d..d4b7acc 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 94a25f7..35eb1dd 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 a8c97eb..57ac435 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
[](https://github.com/karasevm/PrivateDNSAndroid/releases/latest)
[](https://github.com/karasevm/PrivateDNSAndroid/releases/latest)
[](https://apt.izzysoft.de/fdroid/index/apk/ru.karasevm.privatednstoggle)
+[](https://hosted.weblate.org/engage/privatednsandroid/)
# Private DNS Quick Toggle
A quick settings tile to switch your private dns provider. Supports any number of providers. Makes it easy to turn adblocking dns servers on or off with just
@@ -12,11 +13,11 @@ a single tap.
Get the latest apk on the [releases page](https://github.com/karasevm/PrivateDNSAndroid/releases/latest)
or from [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/index/apk/ru.karasevm.privatednstoggle).
-## Automatic (Shizuku)
+### Automatic (Shizuku)
1. Install and start [Shizuku](https://shizuku.rikka.app/).
2. Start the app and allow Shizuku access when prompted.
-## Manual
+### Manual
For the app to work properly you'll need to provide it permissions via ADB:
1. Get to your PC and download platform tools from google [here](https://developer.android.com/studio/releases/platform-tools).
@@ -31,3 +32,18 @@ For the app to work properly you'll need to provide it permissions via ADB:
6. That's it, you should have the app installed.
+## Contributing
+
+### Translation
+The easiest way to contribute would be to submit a translation to your language. Thanks to Weblate gratis hosting for open-source projects you can do it without any programming knowledge on [their website](https://hosted.weblate.org/engage/privatednsandroid/).
+#### Translation status
+
+
+
+
+### 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
deleted file mode 100644
index 2a02e63..0000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,66 +0,0 @@
-plugins {
- id 'com.android.application'
- id 'kotlin-android'
-}
-
-android {
- compileSdk 34
-
- defaultConfig {
- applicationId "ru.karasevm.privatednstoggle"
- versionCode 12
- versionName "1.6"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- targetSdkVersion 34
- minSdkVersion 28
- }
- buildFeatures {
- viewBinding true
- }
- buildTypes {
- release {
- minifyEnabled true
- shrinkResources true
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- debug {
- applicationIdSuffix ".dev"
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
- dependenciesInfo {
- // Disables dependency metadata when building APKs.
- includeInApk = false
- // Disables dependency metadata when building Android App Bundles.
- includeInBundle = false
- }
- namespace 'ru.karasevm.privatednstoggle'
-}
-
-dependencies {
-
- implementation 'androidx.core:core-ktx:1.13.1'
- implementation 'androidx.appcompat:appcompat:1.7.0'
- implementation 'com.google.android.material:material:1.12.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'com.google.guava:guava:33.1.0-android'
- implementation 'com.google.code.gson:gson:2.11.0'
-
- def shizuku_version = '13.1.5'
- implementation "dev.rikka.shizuku:api:$shizuku_version"
- implementation "dev.rikka.shizuku:provider:$shizuku_version"
-
- implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3'
- compileOnly 'dev.rikka.hidden:stub:4.3.2'
-
- 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/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..c06d657
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,88 @@
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ id("com.google.devtools.ksp")
+ id("org.jetbrains.kotlin.plugin.serialization")
+}
+
+android {
+ compileSdk = 35
+ androidResources {
+ generateLocaleConfig = true
+ }
+ defaultConfig {
+ applicationId = "ru.karasevm.privatednstoggle"
+ versionCode = 18
+ versionName = "1.10.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ targetSdk = 35
+ minSdk = 28
+ }
+ buildFeatures {
+ viewBinding = true
+ buildConfig = true
+ }
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ isShrinkResources = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ debug {
+ applicationIdSuffix = ".dev"
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+ dependenciesInfo {
+ // Disables dependency metadata when building APKs.
+ includeInApk = false
+ // Disables dependency metadata when building Android App Bundles.
+ includeInBundle = false
+ }
+ namespace = "ru.karasevm.privatednstoggle"
+}
+
+dependencies {
+ implementation("androidx.core:core-ktx:1.15.0")
+ implementation("androidx.appcompat:appcompat:1.7.0")
+ implementation("androidx.recyclerview:recyclerview:1.4.0")
+ implementation("androidx.activity:activity-ktx:1.10.0")
+ implementation("androidx.fragment:fragment-ktx:1.8.6")
+ implementation("com.google.android.material:material:1.12.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.2.0")
+ implementation("com.google.guava:guava:33.1.0-android")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
+
+ val shizukuVersion = "13.1.5"
+ implementation("dev.rikka.shizuku:api:$shizukuVersion")
+ implementation("dev.rikka.shizuku:provider:$shizukuVersion")
+ compileOnly("dev.rikka.hidden:stub:4.3.3")
+
+ implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
+
+ // Room components
+ val roomVersion = "2.6.1"
+ implementation("androidx.room:room-ktx:$roomVersion")
+ ksp("androidx.room:room-compiler:$roomVersion")
+ androidTestImplementation("androidx.room:room-testing:$roomVersion")
+
+ // Lifecycle components
+ val lifecycleVersion = "2.8.7"
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
+ implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
+
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.2.1")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a695d06..21da365 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,7 +19,7 @@
android:exported="true"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
@@ -41,7 +41,7 @@
()
- private lateinit var sharedPrefs: SharedPreferences
-
- override fun onCreateDialog(
- savedInstanceState: Bundle?
- ): Dialog {
- return activity?.let {
- val builder = MaterialAlertDialogBuilder(it)
- val inflater = requireActivity().layoutInflater
- _binding = SheetDnsSelectorBinding.inflate(inflater)
-
- linearLayoutManager = LinearLayoutManager(context)
- binding.recyclerView.layoutManager = linearLayoutManager
-
- sharedPrefs = defaultPreference(requireContext())
- items = sharedPrefs.dns_servers
- if (items[0] == "") {
- items.removeAt(0)
- items.add("dns.google")
- }
-
- items.add(0, resources.getString(R.string.dns_auto))
- items.add(0, resources.getString(R.string.dns_off))
-
- adapter = RecyclerAdapter(items, false)
- binding.recyclerView.adapter = adapter
-
-
- val startIntent = Intent(context, MainActivity::class.java)
-
- 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()
-
- adapter.onItemClick = { position ->
- when (position) {
- 0 -> {
- PrivateDNSUtils.setPrivateMode(
- requireActivity().contentResolver,
- PrivateDNSUtils.DNS_MODE_OFF
- )
- Toast.makeText(context, R.string.set_to_off_toast, Toast.LENGTH_SHORT).show()
- }
-
- 1 -> {
- PrivateDNSUtils.setPrivateMode(
- requireActivity().contentResolver,
- PrivateDNSUtils.DNS_MODE_AUTO
- )
- Toast.makeText(context, R.string.set_to_auto_toast, Toast.LENGTH_SHORT).show()
- }
-
- else -> {
- val server = items[position].split(" : ").last()
- PrivateDNSUtils.setPrivateMode(
- requireActivity().contentResolver,
- PrivateDNSUtils.DNS_MODE_PRIVATE
- )
- PrivateDNSUtils.setPrivateProvider(
- requireActivity().contentResolver,
- server
- )
- Toast.makeText(
- context,
- getString(R.string.set_to_provider_toast, 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"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/DnsTileService.kt b/app/src/main/java/ru/karasevm/privatednstoggle/DnsTileService.kt
deleted file mode 100644
index 02635c5..0000000
--- a/app/src/main/java/ru/karasevm/privatednstoggle/DnsTileService.kt
+++ /dev/null
@@ -1,299 +0,0 @@
-package ru.karasevm.privatednstoggle
-
-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 androidx.core.content.ContextCompat
-import ru.karasevm.privatednstoggle.utils.DnsServer
-import ru.karasevm.privatednstoggle.utils.PreferenceHelper
-import ru.karasevm.privatednstoggle.utils.PreferenceHelper.autoMode
-import ru.karasevm.privatednstoggle.utils.PreferenceHelper.dns_servers
-import ru.karasevm.privatednstoggle.utils.PreferenceHelper.requireUnlock
-import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils
-import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.AUTO_MODE_OPTION_AUTO
-import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.AUTO_MODE_OPTION_OFF_AUTO
-import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.AUTO_MODE_OPTION_PRIVATE
-import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.DNS_MODE_AUTO
-import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.DNS_MODE_OFF
-import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.DNS_MODE_PRIVATE
-import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.checkForPermission
-
-class DnsTileService : TileService() {
-
- 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 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")
-
- val sharedPrefs = PreferenceHelper.defaultPreference(this)
- if (dnsMode.equals(DNS_MODE_OFF, ignoreCase = true)) {
- if (sharedPrefs.autoMode == AUTO_MODE_OPTION_AUTO || sharedPrefs.autoMode == AUTO_MODE_OPTION_OFF_AUTO) {
- 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)) {
- if (getNextAddress(dnsProvider) == null) {
- if (sharedPrefs.autoMode == AUTO_MODE_OPTION_PRIVATE) {
- changeDNSServer(DNS_MODE_PRIVATE, null)
- } else {
- if (sharedPrefs.autoMode == AUTO_MODE_OPTION_AUTO) {
- changeDNSServer(DNS_MODE_AUTO, dnsProvider)
- } else {
- changeDNSServer(DNS_MODE_OFF, dnsProvider)
- }
- }
- } else {
- changeDNSServer(DNS_MODE_PRIVATE, dnsProvider)
- }
- }
- }
-
- private fun changeDNSServer(server: String, dnsProvider: String?) {
- when (server) {
- 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 -> {
- changeTileState(
- qsTile,
- Tile.STATE_ACTIVE,
- getNextAddress(dnsProvider)?.label,
- R.drawable.ic_private_black_24dp,
- DNS_MODE_PRIVATE,
- getNextAddress(dnsProvider)?.server
- )
- }
- }
- }
-
- override fun onClick() {
- super.onClick()
- if (!checkForPermission(this)) {
- return
- }
- val sharedPrefs = PreferenceHelper.defaultPreference(this)
- val requireUnlock = sharedPrefs.requireUnlock
- if (isLocked && requireUnlock) {
- unlockAndRun(this::cycleState)
- } else {
- cycleState()
- }
-
-
- }
-
- /**
- * Refreshes the state of the tile
- */
- private fun refreshTile() {
- val dnsMode = Settings.Global.getString(contentResolver, "private_dns_mode")
- if (dnsMode.equals(DNS_MODE_OFF, ignoreCase = true)) {
- setTile(
- qsTile,
- Tile.STATE_INACTIVE,
- getString(R.string.dns_off),
- R.drawable.ic_off_black_24dp
- )
- } else if (dnsMode == null) {
- setTile(
- qsTile,
- Tile.STATE_INACTIVE,
- getString(R.string.dns_unknown),
- R.drawable.ic_unknown_black_24dp
- )
- } else if (dnsMode.equals(DNS_MODE_AUTO, ignoreCase = true)) {
- setTile(
- 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")
- val sharedPrefs = PreferenceHelper.defaultPreference(this)
- val items = sharedPrefs.dns_servers.map {
- val parts = it.split(" : ")
- if (parts.size == 2)
- DnsServer(parts[0], parts[1])
- else
- DnsServer(parts[0], parts[0])
- }
-
- if (items.isEmpty() || items[0].server == "") {
- setTile(
- qsTile,
- Tile.STATE_ACTIVE,
- "Google",
- R.drawable.ic_private_black_24dp
- )
- } else {
- val index = items.indexOfFirst { it.server == dnsProvider }
- if (index == -1) {
- setTile(
- qsTile,
- Tile.STATE_ACTIVE,
- dnsProvider,
- R.drawable.ic_private_black_24dp
- )
- } else {
- setTile(
- qsTile,
- Tile.STATE_ACTIVE,
- items[index].label,
- R.drawable.ic_private_black_24dp
- )
- }
- }
- }
- }
-
- private val broadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- refreshTile()
- }
- }
-
- override fun onStartListening() {
- super.onStartListening()
- if (!checkForPermission(this)) {
- return
- }
-
- // Prevent some crashes
- if (qsTile == null) {
- return
- }
- ContextCompat.registerReceiver(
- this,
- broadcastReceiver,
- IntentFilter("refresh_tile"),
- ContextCompat.RECEIVER_NOT_EXPORTED
- )
-
- refreshTile()
-
- }
-
- override fun onStopListening() {
- super.onStopListening()
- unregisterReceiver(broadcastReceiver)
- }
-
- /**
- * 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 preferences,
- * if current address is last returns null
- *
- * @param currentAddress currently set address
- * @return next address
- */
- private fun getNextAddress(currentAddress: String?): DnsServer? {
- val sharedPrefs = PreferenceHelper.defaultPreference(this)
- val items = sharedPrefs.dns_servers.map {
- val parts = it.split(" : ")
- // Assuming string is in the format "$label : $server"
- if (parts.size == 2)
- DnsServer(parts[0], parts[1])
- else
- DnsServer(parts[0], parts[0])
- }.toMutableList()
-
- // Fallback if list is empty
- if (items.isEmpty() || items[0].server == "") {
- items.apply {
- removeAt(0)
- add(DnsServer("Google", "dns.google"))
- }
- }
-
- val index = items.indexOfFirst { it.server == 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
deleted file mode 100644
index 98e096c..0000000
--- a/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt
+++ /dev/null
@@ -1,420 +0,0 @@
-package ru.karasevm.privatednstoggle
-
-import android.Manifest
-import android.app.Activity
-import android.content.ClipData
-import android.content.ClipDescription.MIMETYPE_TEXT_PLAIN
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.pm.IPackageManager
-import android.content.pm.PackageManager
-import android.graphics.Color
-import android.net.Uri
-import android.os.Build
-import android.os.Bundle
-import android.permission.IPermissionManager
-import android.util.Log
-import android.view.Menu
-import android.widget.Toast
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.app.ActivityCompat
-import androidx.core.app.ShareCompat
-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 com.google.gson.Gson
-import com.google.gson.GsonBuilder
-import com.google.gson.JsonSyntaxException
-import com.google.gson.ToNumberPolicy
-import com.google.gson.reflect.TypeToken
-import org.lsposed.hiddenapibypass.HiddenApiBypass
-import rikka.shizuku.Shizuku
-import rikka.shizuku.ShizukuBinderWrapper
-import rikka.shizuku.ShizukuProvider
-import rikka.shizuku.SystemServiceHelper
-import ru.karasevm.privatednstoggle.databinding.ActivityMainBinding
-import ru.karasevm.privatednstoggle.utils.PreferenceHelper
-import ru.karasevm.privatednstoggle.utils.PreferenceHelper.dns_servers
-import ru.karasevm.privatednstoggle.utils.PreferenceHelper.export
-import ru.karasevm.privatednstoggle.utils.PreferenceHelper.import
-
-
-class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogListener,
- DeleteServerDialogFragment.NoticeDialogListener, Shizuku.OnRequestPermissionResultListener {
-
- private lateinit var linearLayoutManager: LinearLayoutManager
- private lateinit var binding: ActivityMainBinding
- private var items = mutableListOf()
- private lateinit var sharedPrefs: SharedPreferences
- private lateinit var adapter: RecyclerAdapter
- private lateinit var clipboard: ClipboardManager
- private lateinit var gson: Gson
-
- private val itemTouchHelper by lazy {
- val simpleItemTouchCallback = object : ItemTouchHelper.SimpleCallback(UP or DOWN, 0) {
-
- override fun onMove(
- recyclerView: RecyclerView,
- viewHolder: RecyclerView.ViewHolder,
- target: RecyclerView.ViewHolder
- ): Boolean {
- adapter.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
- 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)
- }
- }
- }
- ItemTouchHelper(simpleItemTouchCallback)
- }
-
- private fun importSettings(json: String) {
- runCatching {
- val objectType = object : TypeToken