Skip to content

Commit de4a6d6

Browse files
committed
Add feature to export/import all shared preferences
1 parent 51c852f commit de4a6d6

File tree

5 files changed

+196
-14
lines changed

5 files changed

+196
-14
lines changed

app/src/main/java/com/addev/listaspam/SettingsActivity.kt

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
11
package com.addev.listaspam
22

3+
import android.content.Context
4+
import android.net.Uri
35
import android.os.Bundle
6+
import android.widget.Button
7+
import android.widget.Toast
8+
import androidx.activity.result.contract.ActivityResultContracts
49
import androidx.appcompat.app.AppCompatActivity
510
import androidx.preference.PreferenceFragmentCompat
11+
import org.json.JSONArray
12+
import org.json.JSONException
13+
import org.json.JSONObject
14+
import java.io.File
615

716
class SettingsActivity : AppCompatActivity() {
17+
private val exportFileLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri: Uri? ->
18+
uri?.let {
19+
if (exportAllSharedPreferences(it)) {
20+
Toast.makeText(this, "Preferencias exportadas con éxito", Toast.LENGTH_SHORT).show()
21+
} else {
22+
Toast.makeText(this, "Error al exportar preferencias", Toast.LENGTH_SHORT).show()
23+
}
24+
}
25+
}
26+
27+
private val importFileLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
28+
uri?.let {
29+
if (importAllSharedPreferences(it)) {
30+
Toast.makeText(this, "Preferencias importadas con éxito", Toast.LENGTH_SHORT).show()
31+
} else {
32+
Toast.makeText(this, "Error al importar preferencias", Toast.LENGTH_SHORT).show()
33+
}
34+
}
35+
}
36+
837
override fun onCreate(savedInstanceState: Bundle?) {
938
super.onCreate(savedInstanceState)
1039
setContentView(R.layout.activity_settings)
@@ -13,6 +42,17 @@ class SettingsActivity : AppCompatActivity() {
1342
.beginTransaction()
1443
.replace(R.id.settings_container, SettingsFragment())
1544
.commit()
45+
46+
val exportButton: Button = findViewById(R.id.btn_export)
47+
val importButton: Button = findViewById(R.id.btn_import)
48+
49+
exportButton.setOnClickListener {
50+
exportFileLauncher.launch("backup_prefs.json")
51+
}
52+
53+
importButton.setOnClickListener {
54+
importFileLauncher.launch(arrayOf("application/json"))
55+
}
1656
}
1757

1858
// Fragmento para cargar las preferencias
@@ -21,4 +61,117 @@ class SettingsActivity : AppCompatActivity() {
2161
setPreferencesFromResource(R.xml.preferences, rootKey)
2262
}
2363
}
64+
65+
private fun exportAllSharedPreferences(uri: Uri): Boolean {
66+
return try {
67+
val prefsDir = File(applicationInfo.dataDir, "shared_prefs")
68+
val files = prefsDir.listFiles()
69+
val jsonObject = JSONObject()
70+
71+
files?.forEach { file ->
72+
val prefName = file.nameWithoutExtension
73+
val sharedPreferences = getSharedPreferences(prefName, Context.MODE_PRIVATE)
74+
val allEntries: Map<String, *> = sharedPreferences.all
75+
76+
val prefJsonObject = JSONObject()
77+
for ((key, value) in allEntries) {
78+
when (value) {
79+
is Set<*> -> {
80+
val jsonArray = JSONArray(value)
81+
prefJsonObject.put(key, jsonArray)
82+
}
83+
else -> prefJsonObject.put(key, value)
84+
}
85+
}
86+
87+
jsonObject.put(prefName, prefJsonObject)
88+
}
89+
90+
contentResolver.openOutputStream(uri)?.use { outputStream ->
91+
outputStream.write(jsonObject.toString().toByteArray())
92+
}
93+
true
94+
} catch (e: Exception) {
95+
e.printStackTrace()
96+
false
97+
}
98+
}
99+
100+
private fun importAllSharedPreferences(uri: Uri): Boolean {
101+
return try {
102+
contentResolver.openInputStream(uri)?.use { inputStream ->
103+
val jsonString = inputStream.bufferedReader().use { it.readText() }
104+
val jsonObject = JSONObject(jsonString)
105+
106+
// Validar el archivo JSON antes de importar
107+
if (!isValidSharedPreferencesJson(jsonObject)) {
108+
Toast.makeText(this, "El archivo JSON no es válido", Toast.LENGTH_SHORT).show()
109+
return false
110+
}
111+
112+
for (prefName in jsonObject.keys()) {
113+
val sharedPreferences = getSharedPreferences(prefName, Context.MODE_PRIVATE)
114+
val editor = sharedPreferences.edit()
115+
116+
val prefJsonObject = jsonObject.getJSONObject(prefName)
117+
for (key in prefJsonObject.keys()) {
118+
val value = prefJsonObject.get(key)
119+
when (value) {
120+
is JSONArray -> {
121+
val set = mutableSetOf<String>()
122+
for (i in 0 until value.length()) {
123+
set.add(value.getString(i))
124+
}
125+
editor.putStringSet(key, set)
126+
}
127+
is Int -> editor.putInt(key, value)
128+
is Long -> editor.putLong(key, value)
129+
is Float -> editor.putFloat(key, value)
130+
is Boolean -> editor.putBoolean(key, value)
131+
is String -> editor.putString(key, value)
132+
else -> throw IllegalArgumentException("Tipo no soportado")
133+
}
134+
}
135+
editor.apply()
136+
}
137+
}
138+
true
139+
} catch (e: Exception) {
140+
e.printStackTrace()
141+
false
142+
}
143+
}
144+
145+
private fun isValidSharedPreferencesJson(jsonObject: JSONObject): Boolean {
146+
return try {
147+
for (prefName in jsonObject.keys()) {
148+
val prefJsonObject = jsonObject.getJSONObject(prefName)
149+
150+
// Verificar cada valor en las SharedPreferences
151+
for (key in prefJsonObject.keys()) {
152+
val value = prefJsonObject.get(key)
153+
when (value) {
154+
is JSONArray -> {
155+
// Verificar que el JSONArray contiene solo Strings
156+
for (i in 0 until value.length()) {
157+
if (value.get(i) !is String) {
158+
return false
159+
}
160+
}
161+
}
162+
is Int, is Long, is Float, is Boolean, is String -> {
163+
// Tipos válidos, no hacer nada
164+
}
165+
else -> {
166+
return false
167+
}
168+
}
169+
}
170+
}
171+
true
172+
} catch (e: JSONException) {
173+
e.printStackTrace()
174+
false
175+
}
176+
}
24177
}

app/src/main/java/com/addev/listaspam/fragment/SettingsFragment.kt

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.addev.listaspam.util
2+
3+
import android.content.Context
4+
import android.content.SharedPreferences
5+
import org.json.JSONObject
6+
import java.io.File
7+
import java.io.FileInputStream
8+
import java.io.FileOutputStream
9+
Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
2+
3+
4+
<LinearLayout
5+
xmlns:android="http://schemas.android.com/apk/res/android"
6+
android:layout_width="match_parent"
7+
android:layout_height="match_parent"
8+
android:orientation="vertical">
9+
10+
<FrameLayout
311
android:id="@+id/settings_container"
412
android:layout_width="match_parent"
5-
android:layout_height="match_parent" />
13+
android:layout_height="wrap_content" />
14+
15+
<LinearLayout
16+
android:layout_width="match_parent"
17+
android:layout_height="match_parent"
18+
android:orientation="vertical"
19+
android:padding="16dp"
20+
android:gravity="center">
21+
22+
<Button
23+
android:id="@+id/btn_export"
24+
android:layout_width="wrap_content"
25+
android:layout_height="wrap_content"
26+
android:text="Exportar configuración y listas" />
27+
28+
<Button
29+
android:id="@+id/btn_import"
30+
android:layout_width="wrap_content"
31+
android:layout_height="wrap_content"
32+
android:text="Importar configuración y listas"
33+
android:layout_marginTop="16dp" />
34+
</LinearLayout>
35+
36+
</LinearLayout>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<resources xmlns:tools="http://schemas.android.com/tools">
22
<!-- Base application theme. -->
3-
<style name="Base.Theme.ListaSpam" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
3+
<style name="Base.Theme.ListaSpam" parent="Theme.MaterialComponents.DayNight">
44
<!-- Customize your light theme here. -->
55
<item name="colorPrimary">@color/black</item>
66
</style>

0 commit comments

Comments
 (0)