1
1
package ru.karasevm.privatednstoggle
2
2
3
3
import android.Manifest
4
+ import android.app.Activity
4
5
import android.content.ClipData
5
6
import android.content.ClipDescription.MIMETYPE_TEXT_PLAIN
6
7
import android.content.ClipboardManager
@@ -17,8 +18,10 @@ import android.permission.IPermissionManager
17
18
import android.util.Log
18
19
import android.view.Menu
19
20
import android.widget.Toast
21
+ import androidx.activity.result.contract.ActivityResultContracts
20
22
import androidx.appcompat.app.AppCompatActivity
21
23
import androidx.core.app.ActivityCompat
24
+ import androidx.core.app.ShareCompat
22
25
import androidx.recyclerview.widget.ItemTouchHelper
23
26
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
24
27
import androidx.recyclerview.widget.ItemTouchHelper.UP
@@ -95,6 +98,34 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
95
98
ItemTouchHelper (simpleItemTouchCallback)
96
99
}
97
100
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
+
98
129
override fun onCreate (savedInstanceState : Bundle ? ) {
99
130
super .onCreate(savedInstanceState)
100
131
@@ -145,7 +176,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
145
176
true
146
177
}
147
178
148
- R .id.export_settings -> {
179
+ R .id.export_settings_clipboard -> {
149
180
val data = sharedPrefs.export()
150
181
val jsonData = gson.toJson(data)
151
182
clipboard.setPrimaryClip(ClipData .newPlainText(" " , jsonData))
@@ -156,39 +187,39 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
156
187
true
157
188
}
158
189
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 -> {
160
218
val clipData = clipboard.primaryClip?.getItemAt(0 )
161
219
val textData = clipData?.text
162
220
163
221
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())
192
223
}
193
224
true
194
225
}
@@ -204,6 +235,45 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
204
235
}
205
236
}
206
237
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
+
207
277
override fun onCreateOptionsMenu (menu : Menu ? ): Boolean {
208
278
menuInflater.inflate(R .menu.menu_main, menu)
209
279
return true
@@ -248,7 +318,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
248
318
super .onWindowFocusChanged(hasFocus)
249
319
if (! hasFocus) {
250
320
// 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 )
252
322
253
323
// If the clipboard doesn't contain data, disable the paste menu item.
254
324
// If it does contain data, decide whether you can handle the data.
0 commit comments