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/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt b/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt
index 246883c..c05b39e 100644
--- a/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt
+++ b/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt
@@ -14,7 +14,7 @@ import com.google.common.net.InternetDomainName
import ru.karasevm.privatednstoggle.databinding.DialogAddBinding
-class AddServerDialogFragment : DialogFragment() {
+class AddServerDialogFragment(private val position: Int?, private val label: String?, private val server: String?) : DialogFragment() {
// Use this instance of the interface to deliver action events
private lateinit var listener: NoticeDialogListener
@@ -29,6 +29,8 @@ class AddServerDialogFragment : DialogFragment() {
* Each method passes the DialogFragment in case the host needs to query it. */
interface NoticeDialogListener {
fun onDialogPositiveClick(label: String? ,server: String)
+ fun onDialogPositiveClick(label: String?, server: String, position: Int)
+ fun onDeleteItemClicked(position: Int)
}
// Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
@@ -59,22 +61,47 @@ class AddServerDialogFragment : DialogFragment() {
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.menu_add
- ) { _, _ ->
- listener.onDialogPositiveClick(
- binding.editTextServerHint.text.toString().trim(),
- binding.editTextServerAddr.text.toString().trim()
- )
- }
- .setNegativeButton(
- R.string.cancel
- ) { _, _ ->
- dialog?.cancel()
- }
+ if (position != null) {
+ binding.editTextServerHint.setText(label)
+ binding.editTextServerAddr.setText(server)
+ builder.setTitle(R.string.edit_server).setView(view)
+ .setPositiveButton(
+ R.string.menu_save
+ ) { _, _ ->
+ listener.onDialogPositiveClick(
+ binding.editTextServerHint.text.toString().trim(),
+ binding.editTextServerAddr.text.toString().trim(),
+ position)
+ }
+ .setNegativeButton(
+ R.string.cancel
+ ) { _, _ ->
+ dialog?.cancel()
+ }
+ .setNeutralButton(
+ R.string.delete
+ ) { _, _ ->
+ listener.onDeleteItemClicked(position)
+ }
+ }
+ else {
+ builder.setTitle(R.string.add_server)
+ .setView(view)
+ // Add action buttons
+ .setPositiveButton(
+ R.string.menu_add
+ ) { _, _ ->
+ listener.onDialogPositiveClick(
+ binding.editTextServerHint.text.toString().trim(),
+ binding.editTextServerAddr.text.toString().trim()
+ )
+ }
+ .setNegativeButton(
+ R.string.cancel
+ ) { _, _ ->
+ dialog?.cancel()
+ }
+ }
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/DNSServerDialogFragment.kt b/app/src/main/java/ru/karasevm/privatednstoggle/DNSServerDialogFragment.kt
index 4666321..3ea8912 100644
--- a/app/src/main/java/ru/karasevm/privatednstoggle/DNSServerDialogFragment.kt
+++ b/app/src/main/java/ru/karasevm/privatednstoggle/DNSServerDialogFragment.kt
@@ -44,7 +44,7 @@ class DNSServerDialogFragment : DialogFragment() {
items.add(0, resources.getString(R.string.dns_auto))
items.add(0, resources.getString(R.string.dns_off))
- adapter = RecyclerAdapter(items, false)
+ adapter = RecyclerAdapter(items, false) {}
binding.recyclerView.adapter = adapter
diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt b/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt
index 98e096c..230eadb 100644
--- a/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt
+++ b/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt
@@ -1,11 +1,9 @@
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
@@ -17,6 +15,7 @@ import android.os.Bundle
import android.permission.IPermissionManager
import android.util.Log
import android.view.Menu
+import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
@@ -139,27 +138,41 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
binding.recyclerView.layoutManager = linearLayoutManager
sharedPrefs = PreferenceHelper.defaultPreference(this)
- clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
gson = GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create()
items = sharedPrefs.dns_servers
if (items[0] == "") {
items.removeAt(0)
}
- adapter = RecyclerAdapter(items, true)
+
+ updateEmptyView()
+ adapter = RecyclerAdapter(items, true) { updateEmptyView() }
adapter.onItemClick = { position ->
- val newFragment = DeleteServerDialogFragment(position)
- newFragment.show(supportFragmentManager, "delete_server")
+ val data = items[position].split(" : ")
+ val label: String?
+ val server: String
+ if (data.size == 2) {
+ label = data[0]
+ server = data[1]
+ }
+ else {
+ label = null
+ server = data[0]
+ }
+ val newFragment = AddServerDialogFragment(position, label, server)
+ newFragment.show(supportFragmentManager, "edit_server")
}
adapter.onItemsChanged = { swappedItems ->
items = swappedItems
sharedPrefs.dns_servers = swappedItems
+ updateEmptyView()
}
adapter.onDragStart = { viewHolder ->
itemTouchHelper.startDrag(viewHolder)
}
binding.floatingActionButton.setOnClickListener {
- val newFragment = AddServerDialogFragment()
+ val newFragment = AddServerDialogFragment(null, null, null)
newFragment.show(supportFragmentManager, "add_server")
}
binding.recyclerView.adapter = adapter
@@ -235,9 +248,19 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
}
}
+ private fun updateEmptyView() {
+ if (items.isEmpty()) {
+ binding.emptyView.visibility = View.VISIBLE
+ binding.emptyViewHint.visibility = View.VISIBLE
+ } else {
+ binding.emptyView.visibility = View.GONE
+ binding.emptyViewHint.visibility = View.GONE
+ }
+ }
+
private var saveResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- if (result.resultCode == Activity.RESULT_OK) {
+ if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
data?.data?.also { uri ->
val jsonData = gson.toJson(sharedPrefs.export())
@@ -262,7 +285,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
private var importResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- if (result.resultCode == Activity.RESULT_OK) {
+ if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
data?.data?.also { uri ->
val contentResolver = applicationContext.contentResolver
@@ -336,6 +359,11 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
Shizuku.removeRequestPermissionResultListener(this::onRequestPermissionResult)
}
+ override fun onDeleteItemClicked(position: Int) {
+ val newFragment = DeleteServerDialogFragment(position)
+ newFragment.show(supportFragmentManager, "delete_server")
+ }
+
override fun onDialogPositiveClick(label: String?, server: String) {
if (server.isEmpty()) {
Toast.makeText(this, R.string.server_length_error, Toast.LENGTH_SHORT).show()
@@ -358,6 +386,21 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
sharedPrefs.dns_servers = items
}
+ override fun onDialogPositiveClick(label: String?, server: String, position: Int) {
+ if (server.isEmpty()) {
+ Toast.makeText(this, R.string.server_length_error, Toast.LENGTH_SHORT).show()
+ return
+ }
+ if (label.isNullOrEmpty()) {
+ items[position] = server
+ } else {
+ items[position] = "$label : $server"
+ }
+ adapter.notifyItemChanged(position)
+ sharedPrefs.dns_servers = items
+ binding.recyclerView.adapter?.notifyItemChanged(position)
+ }
+
/**
* Attempts to grant WRITE_SECURE_SETTINGS permission with Shizuku
*/
diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt b/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt
index c4c1730..0d61cfc 100644
--- a/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt
+++ b/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt
@@ -10,7 +10,7 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import java.util.Collections
-class RecyclerAdapter(private val items: MutableList, private val showDragHandle: Boolean) :
+class RecyclerAdapter(private val items: MutableList, private val showDragHandle: Boolean, private val onDataChanged: () -> Unit) :
RecyclerView.Adapter() {
var onItemClick: ((Int) -> Unit)? = null
@@ -77,6 +77,7 @@ class RecyclerAdapter(private val items: MutableList, private val showDr
clear()
addAll(newItems)
}
+ onDataChanged()
}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 3b78639..eb0201d 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -30,6 +30,30 @@
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/topAppBarLayout" />
+
+
+
+
Unknown
Add Server
Add
+ Save
Privacy Policy
Select Server
Done
@@ -43,4 +44,7 @@
To file
Saving failed
Saved successfully
+ Edit server
+ No Servers Added
+ Tap on the button below to add one
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 01ac801..5a61030 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.5.0'
+ classpath 'com.android.tools.build:gradle:8.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22"
// NOTE: Do not place your application dependencies here; they belong