Skip to content

Commit

Permalink
Add more ways to export/import settings
Browse files Browse the repository at this point in the history
  • Loading branch information
karasevm committed Jul 2, 2024
1 parent a6ed85e commit 213d3e4
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 36 deletions.
132 changes: 101 additions & 31 deletions app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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
Expand All @@ -17,8 +18,10 @@ 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
Expand Down Expand Up @@ -95,6 +98,34 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
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?) {
super.onCreate(savedInstanceState)

Expand Down Expand Up @@ -145,7 +176,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
true
}

R.id.export_settings -> {
R.id.export_settings_clipboard -> {
val data = sharedPrefs.export()
val jsonData = gson.toJson(data)
clipboard.setPrimaryClip(ClipData.newPlainText("", jsonData))
Expand All @@ -156,39 +187,39 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
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 textData = clipData?.text

if (textData != null) {
runCatching {
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()
}
}
}
importSettings(textData.toString())
}
true
}
Expand All @@ -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 {
menuInflater.inflate(R.menu.menu_main, menu)
return true
Expand Down Expand Up @@ -248,7 +318,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
super.onWindowFocusChanged(hasFocus)
if (!hasFocus) {
// 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 it does contain data, decide whether you can handle the data.
Expand Down
27 changes: 24 additions & 3 deletions app/src/main/res/menu/menu_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,33 @@
app:showAsAction="ifRoom"
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"
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"
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"
android:title="@string/menu_privacy_policy"
app:showAsAction="never" />
Expand Down
11 changes: 9 additions & 2 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@
<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="a11y_drag_handle">Drag handle</string>
<string name="menu_import">Import from clipboard</string>
<string name="menu_export">Export to clipboard</string>
<string name="menu_import">Import</string>
<string name="menu_export">Export</string>
<string name="import_success">Imported</string>
<string name="import_failure">Import failed</string>
<string name="import_failure_json">Import failed, malformed JSON</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>

0 comments on commit 213d3e4

Please sign in to comment.