Skip to content

Commit 213d3e4

Browse files
committed
Add more ways to export/import settings
1 parent a6ed85e commit 213d3e4

File tree

3 files changed

+134
-36
lines changed

3 files changed

+134
-36
lines changed

app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt

Lines changed: 101 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ru.karasevm.privatednstoggle
22

33
import android.Manifest
4+
import android.app.Activity
45
import android.content.ClipData
56
import android.content.ClipDescription.MIMETYPE_TEXT_PLAIN
67
import android.content.ClipboardManager
@@ -17,8 +18,10 @@ import android.permission.IPermissionManager
1718
import android.util.Log
1819
import android.view.Menu
1920
import android.widget.Toast
21+
import androidx.activity.result.contract.ActivityResultContracts
2022
import androidx.appcompat.app.AppCompatActivity
2123
import androidx.core.app.ActivityCompat
24+
import androidx.core.app.ShareCompat
2225
import androidx.recyclerview.widget.ItemTouchHelper
2326
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
2427
import androidx.recyclerview.widget.ItemTouchHelper.UP
@@ -95,6 +98,34 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
9598
ItemTouchHelper(simpleItemTouchCallback)
9699
}
97100

101+
private fun importSettings(json: String) {
102+
runCatching {
103+
val objectType = object : TypeToken<Map<String, Any>>() {}.type
104+
val data: Map<String, Any> = gson.fromJson(json, objectType)
105+
sharedPrefs.import(data)
106+
}.onSuccess {
107+
Toast.makeText(
108+
this, getString(R.string.import_success), Toast.LENGTH_SHORT
109+
).show()
110+
ActivityCompat.recreate(this)
111+
}.onFailure { exception ->
112+
Log.e("IMPORT", "Import failed", exception)
113+
when (exception) {
114+
is JsonSyntaxException -> {
115+
Toast.makeText(
116+
this, getString(R.string.import_failure_json), Toast.LENGTH_SHORT
117+
).show()
118+
}
119+
120+
else -> {
121+
Toast.makeText(
122+
this, getString(R.string.import_failure), Toast.LENGTH_SHORT
123+
).show()
124+
}
125+
}
126+
}
127+
}
128+
98129
override fun onCreate(savedInstanceState: Bundle?) {
99130
super.onCreate(savedInstanceState)
100131

@@ -145,7 +176,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
145176
true
146177
}
147178

148-
R.id.export_settings -> {
179+
R.id.export_settings_clipboard -> {
149180
val data = sharedPrefs.export()
150181
val jsonData = gson.toJson(data)
151182
clipboard.setPrimaryClip(ClipData.newPlainText("", jsonData))
@@ -156,39 +187,39 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
156187
true
157188
}
158189

159-
R.id.import_settings -> {
190+
R.id.export_settings_share -> {
191+
val data = sharedPrefs.export()
192+
val jsonData = gson.toJson(data)
193+
ShareCompat.IntentBuilder(this).setText(jsonData).setType("text/plain")
194+
.startChooser()
195+
true
196+
}
197+
198+
R.id.export_settings_file -> {
199+
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
200+
addCategory(Intent.CATEGORY_OPENABLE)
201+
type = "text/plain"
202+
putExtra(Intent.EXTRA_TITLE, "private-dns-export")
203+
}
204+
saveResultLauncher.launch(intent)
205+
true
206+
}
207+
208+
R.id.import_settings_file -> {
209+
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
210+
addCategory(Intent.CATEGORY_OPENABLE)
211+
type = "text/plain"
212+
}
213+
importResultLauncher.launch(intent)
214+
true
215+
}
216+
217+
R.id.import_settings_clipboard -> {
160218
val clipData = clipboard.primaryClip?.getItemAt(0)
161219
val textData = clipData?.text
162220

163221
if (textData != null) {
164-
runCatching {
165-
val jsonData = textData.toString()
166-
val objectType = object : TypeToken<Map<String, Any>>() {}.type
167-
val data: Map<String, Any> = gson.fromJson(jsonData, objectType)
168-
sharedPrefs.import(data)
169-
}.onSuccess {
170-
Toast.makeText(
171-
this, getString(R.string.import_success), Toast.LENGTH_SHORT
172-
).show()
173-
ActivityCompat.recreate(this)
174-
}.onFailure { exception ->
175-
Log.e("IMPORT", "Import failed", exception)
176-
when (exception) {
177-
is JsonSyntaxException -> {
178-
Toast.makeText(
179-
this,
180-
getString(R.string.import_failure_json),
181-
Toast.LENGTH_SHORT
182-
).show()
183-
}
184-
185-
else -> {
186-
Toast.makeText(
187-
this, getString(R.string.import_failure), Toast.LENGTH_SHORT
188-
).show()
189-
}
190-
}
191-
}
222+
importSettings(textData.toString())
192223
}
193224
true
194225
}
@@ -204,6 +235,45 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
204235
}
205236
}
206237

238+
private var saveResultLauncher =
239+
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
240+
if (result.resultCode == Activity.RESULT_OK) {
241+
val data: Intent? = result.data
242+
data?.data?.also { uri ->
243+
val jsonData = gson.toJson(sharedPrefs.export())
244+
val contentResolver = applicationContext.contentResolver
245+
runCatching {
246+
contentResolver.openOutputStream(uri)?.use { outputStream ->
247+
outputStream.write(jsonData.toByteArray())
248+
}
249+
}.onFailure { exception ->
250+
Log.e("EXPORT", "Export failed", exception)
251+
Toast.makeText(
252+
this, getString(R.string.export_failure), Toast.LENGTH_SHORT
253+
).show()
254+
}.onSuccess {
255+
Toast.makeText(
256+
this, getString(R.string.export_success), Toast.LENGTH_SHORT
257+
).show()
258+
}
259+
}
260+
}
261+
}
262+
263+
private var importResultLauncher =
264+
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
265+
if (result.resultCode == Activity.RESULT_OK) {
266+
val data: Intent? = result.data
267+
data?.data?.also { uri ->
268+
val contentResolver = applicationContext.contentResolver
269+
contentResolver.openInputStream(uri)?.use { inputStream ->
270+
val jsonData = inputStream.bufferedReader().use { it.readText() }
271+
importSettings(jsonData)
272+
}
273+
}
274+
}
275+
}
276+
207277
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
208278
menuInflater.inflate(R.menu.menu_main, menu)
209279
return true
@@ -248,7 +318,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
248318
super.onWindowFocusChanged(hasFocus)
249319
if (!hasFocus) {
250320
// Gets the ID of the "paste" menu item.
251-
val pasteItem = binding.topAppBar.menu.findItem(R.id.import_settings)
321+
val pasteItem = binding.topAppBar.menu.findItem(R.id.import_settings_clipboard)
252322

253323
// If the clipboard doesn't contain data, disable the paste menu item.
254324
// If it does contain data, decide whether you can handle the data.

app/src/main/res/menu/menu_main.xml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,33 @@
77
app:showAsAction="ifRoom"
88
android:icon="@drawable/ic_baseline_settings_24"
99
/>
10-
<item android:id="@+id/import_settings"
10+
<item android:id="@+id/import_settings_submenu"
1111
android:title="@string/menu_import"
12-
app:showAsAction="never" />
12+
app:showAsAction="never" >
13+
<menu>
14+
<item android:id="@+id/import_settings_file"
15+
android:title="@string/menu_import_from_file"
16+
app:showAsAction="never" />
17+
<item android:id="@+id/import_settings_clipboard"
18+
android:title="@string/menu_import_from_clipboard"
19+
app:showAsAction="never" />
20+
</menu>
21+
</item>
1322
<item android:id="@+id/export_settings"
1423
android:title="@string/menu_export"
15-
app:showAsAction="never" />
24+
app:showAsAction="never" >
25+
<menu>
26+
<item android:id="@+id/export_settings_clipboard"
27+
android:title="@string/menu_export_to_clipboard"
28+
app:showAsAction="never" />
29+
<item android:id="@+id/export_settings_share"
30+
android:title="@string/menu_export_share"
31+
app:showAsAction="never" />
32+
<item android:id="@+id/export_settings_file"
33+
android:title="@string/menu_export_to_file"
34+
app:showAsAction="never" />
35+
</menu>
36+
</item>
1637
<item android:id="@+id/privacy_policy"
1738
android:title="@string/menu_privacy_policy"
1839
app:showAsAction="never" />

app/src/main/res/values/strings.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,17 @@
3030
<string name="set_to_provider_toast">Private DNS set to %1$s</string>
3131
<string name="require_unlock_setting">Require unlocking the device to change server</string>
3232
<string name="a11y_drag_handle">Drag handle</string>
33-
<string name="menu_import">Import from clipboard</string>
34-
<string name="menu_export">Export to clipboard</string>
33+
<string name="menu_import">Import</string>
34+
<string name="menu_export">Export</string>
3535
<string name="import_success">Imported</string>
3636
<string name="import_failure">Import failed</string>
3737
<string name="import_failure_json">Import failed, malformed JSON</string>
3838
<string name="copy_success">Copied</string>
39+
<string name="menu_import_from_file">From file</string>
40+
<string name="menu_import_from_clipboard">From clipboard</string>
41+
<string name="menu_export_to_clipboard">To clipboard</string>
42+
<string name="menu_export_share">Share</string>
43+
<string name="menu_export_to_file">To file</string>
44+
<string name="export_failure">Saving failed</string>
45+
<string name="export_success">Saved successfully</string>
3946
</resources>

0 commit comments

Comments
 (0)