mirror of
https://github.com/karasevm/PrivateDNSAndroid.git
synced 2025-06-29 04:39:56 +00:00
Add more ways to export/import settings
This commit is contained in:
parent
a6ed85e2c8
commit
213d3e4dee
3 changed files with 134 additions and 36 deletions
|
@ -1,6 +1,7 @@
|
||||||
package ru.karasevm.privatednstoggle
|
package ru.karasevm.privatednstoggle
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipDescription.MIMETYPE_TEXT_PLAIN
|
import android.content.ClipDescription.MIMETYPE_TEXT_PLAIN
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
|
@ -17,8 +18,10 @@ import android.permission.IPermissionManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.app.ShareCompat
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
|
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper.UP
|
import androidx.recyclerview.widget.ItemTouchHelper.UP
|
||||||
|
@ -95,6 +98,34 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
|
||||||
ItemTouchHelper(simpleItemTouchCallback)
|
ItemTouchHelper(simpleItemTouchCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun importSettings(json: String) {
|
||||||
|
runCatching {
|
||||||
|
val objectType = object : TypeToken<Map<String, Any>>() {}.type
|
||||||
|
val data: Map<String, Any> = gson.fromJson(json, objectType)
|
||||||
|
sharedPrefs.import(data)
|
||||||
|
}.onSuccess {
|
||||||
|
Toast.makeText(
|
||||||
|
this, getString(R.string.import_success), Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
ActivityCompat.recreate(this)
|
||||||
|
}.onFailure { exception ->
|
||||||
|
Log.e("IMPORT", "Import failed", exception)
|
||||||
|
when (exception) {
|
||||||
|
is JsonSyntaxException -> {
|
||||||
|
Toast.makeText(
|
||||||
|
this, getString(R.string.import_failure_json), Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Toast.makeText(
|
||||||
|
this, getString(R.string.import_failure), Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -145,7 +176,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.export_settings -> {
|
R.id.export_settings_clipboard -> {
|
||||||
val data = sharedPrefs.export()
|
val data = sharedPrefs.export()
|
||||||
val jsonData = gson.toJson(data)
|
val jsonData = gson.toJson(data)
|
||||||
clipboard.setPrimaryClip(ClipData.newPlainText("", jsonData))
|
clipboard.setPrimaryClip(ClipData.newPlainText("", jsonData))
|
||||||
|
@ -156,39 +187,39 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.import_settings -> {
|
R.id.export_settings_share -> {
|
||||||
|
val data = sharedPrefs.export()
|
||||||
|
val jsonData = gson.toJson(data)
|
||||||
|
ShareCompat.IntentBuilder(this).setText(jsonData).setType("text/plain")
|
||||||
|
.startChooser()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.export_settings_file -> {
|
||||||
|
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 clipData = clipboard.primaryClip?.getItemAt(0)
|
||||||
val textData = clipData?.text
|
val textData = clipData?.text
|
||||||
|
|
||||||
if (textData != null) {
|
if (textData != null) {
|
||||||
runCatching {
|
importSettings(textData.toString())
|
||||||
val jsonData = textData.toString()
|
|
||||||
val objectType = object : TypeToken<Map<String, Any>>() {}.type
|
|
||||||
val data: Map<String, Any> = gson.fromJson(jsonData, objectType)
|
|
||||||
sharedPrefs.import(data)
|
|
||||||
}.onSuccess {
|
|
||||||
Toast.makeText(
|
|
||||||
this, getString(R.string.import_success), Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
ActivityCompat.recreate(this)
|
|
||||||
}.onFailure { exception ->
|
|
||||||
Log.e("IMPORT", "Import failed", exception)
|
|
||||||
when (exception) {
|
|
||||||
is JsonSyntaxException -> {
|
|
||||||
Toast.makeText(
|
|
||||||
this,
|
|
||||||
getString(R.string.import_failure_json),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
Toast.makeText(
|
|
||||||
this, getString(R.string.import_failure), Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -204,6 +235,45 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var saveResultLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
val data: Intent? = result.data
|
||||||
|
data?.data?.also { uri ->
|
||||||
|
val jsonData = gson.toJson(sharedPrefs.export())
|
||||||
|
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 == Activity.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 {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_main, menu)
|
menuInflater.inflate(R.menu.menu_main, menu)
|
||||||
return true
|
return true
|
||||||
|
@ -248,7 +318,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
|
||||||
super.onWindowFocusChanged(hasFocus)
|
super.onWindowFocusChanged(hasFocus)
|
||||||
if (!hasFocus) {
|
if (!hasFocus) {
|
||||||
// Gets the ID of the "paste" menu item.
|
// Gets the ID of the "paste" menu item.
|
||||||
val pasteItem = binding.topAppBar.menu.findItem(R.id.import_settings)
|
val pasteItem = binding.topAppBar.menu.findItem(R.id.import_settings_clipboard)
|
||||||
|
|
||||||
// If the clipboard doesn't contain data, disable the paste menu item.
|
// If the clipboard doesn't contain data, disable the paste menu item.
|
||||||
// If it does contain data, decide whether you can handle the data.
|
// If it does contain data, decide whether you can handle the data.
|
||||||
|
|
|
@ -7,12 +7,33 @@
|
||||||
app:showAsAction="ifRoom"
|
app:showAsAction="ifRoom"
|
||||||
android:icon="@drawable/ic_baseline_settings_24"
|
android:icon="@drawable/ic_baseline_settings_24"
|
||||||
/>
|
/>
|
||||||
<item android:id="@+id/import_settings"
|
<item android:id="@+id/import_settings_submenu"
|
||||||
android:title="@string/menu_import"
|
android:title="@string/menu_import"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" >
|
||||||
|
<menu>
|
||||||
|
<item android:id="@+id/import_settings_file"
|
||||||
|
android:title="@string/menu_import_from_file"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
<item android:id="@+id/import_settings_clipboard"
|
||||||
|
android:title="@string/menu_import_from_clipboard"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
<item android:id="@+id/export_settings"
|
<item android:id="@+id/export_settings"
|
||||||
android:title="@string/menu_export"
|
android:title="@string/menu_export"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" >
|
||||||
|
<menu>
|
||||||
|
<item android:id="@+id/export_settings_clipboard"
|
||||||
|
android:title="@string/menu_export_to_clipboard"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
<item android:id="@+id/export_settings_share"
|
||||||
|
android:title="@string/menu_export_share"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
<item android:id="@+id/export_settings_file"
|
||||||
|
android:title="@string/menu_export_to_file"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
<item android:id="@+id/privacy_policy"
|
<item android:id="@+id/privacy_policy"
|
||||||
android:title="@string/menu_privacy_policy"
|
android:title="@string/menu_privacy_policy"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
|
@ -30,10 +30,17 @@
|
||||||
<string name="set_to_provider_toast">Private DNS set to %1$s</string>
|
<string name="set_to_provider_toast">Private DNS set to %1$s</string>
|
||||||
<string name="require_unlock_setting">Require unlocking the device to change server</string>
|
<string name="require_unlock_setting">Require unlocking the device to change server</string>
|
||||||
<string name="a11y_drag_handle">Drag handle</string>
|
<string name="a11y_drag_handle">Drag handle</string>
|
||||||
<string name="menu_import">Import from clipboard</string>
|
<string name="menu_import">Import</string>
|
||||||
<string name="menu_export">Export to clipboard</string>
|
<string name="menu_export">Export</string>
|
||||||
<string name="import_success">Imported</string>
|
<string name="import_success">Imported</string>
|
||||||
<string name="import_failure">Import failed</string>
|
<string name="import_failure">Import failed</string>
|
||||||
<string name="import_failure_json">Import failed, malformed JSON</string>
|
<string name="import_failure_json">Import failed, malformed JSON</string>
|
||||||
<string name="copy_success">Copied</string>
|
<string name="copy_success">Copied</string>
|
||||||
|
<string name="menu_import_from_file">From file</string>
|
||||||
|
<string name="menu_import_from_clipboard">From clipboard</string>
|
||||||
|
<string name="menu_export_to_clipboard">To clipboard</string>
|
||||||
|
<string name="menu_export_share">Share</string>
|
||||||
|
<string name="menu_export_to_file">To file</string>
|
||||||
|
<string name="export_failure">Saving failed</string>
|
||||||
|
<string name="export_success">Saved successfully</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue