Skip to content

Commit 0d8164f

Browse files
committed
add some ksdocs
1 parent f8fed7d commit 0d8164f

File tree

3 files changed

+158
-33
lines changed

3 files changed

+158
-33
lines changed

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.addev.listaspam.util.getWhitelistNumbers
3535
import com.addev.listaspam.util.setListaSpamApiLang
3636
import com.addev.listaspam.util.setTellowsApiCountry
3737
import java.util.Locale
38+
import androidx.core.net.toUri
3839

3940
class MainActivity : AppCompatActivity(), CallLogAdapter.OnItemChangedListener {
4041

@@ -82,13 +83,13 @@ class MainActivity : AppCompatActivity(), CallLogAdapter.OnItemChangedListener {
8283
}
8384

8485
R.id.action_about -> {
85-
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(ABOUT_LINK))
86+
val intent = Intent(Intent.ACTION_VIEW, ABOUT_LINK.toUri())
8687
this.startActivity(intent)
8788
true
8889
}
8990

9091
R.id.donate -> {
91-
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(DONATE_LINK))
92+
val intent = Intent(Intent.ACTION_VIEW, DONATE_LINK.toUri())
9293
this.startActivity(intent)
9394
true
9495
}
@@ -140,7 +141,7 @@ class MainActivity : AppCompatActivity(), CallLogAdapter.OnItemChangedListener {
140141
builder.setTitle(getString(R.string.test_number))
141142

142143
val input = EditText(this)
143-
input.inputType = InputType.TYPE_CLASS_PHONE // Para números telefónicos
144+
input.inputType = InputType.TYPE_CLASS_PHONE
144145
builder.setView(input)
145146

146147
builder.setPositiveButton(getString(R.string.aceptar)) { dialog, _ ->
@@ -236,6 +237,21 @@ class MainActivity : AppCompatActivity(), CallLogAdapter.OnItemChangedListener {
236237
Toast.makeText(context, message, duration).show()
237238
}
238239

240+
/**
241+
* Checks for necessary permissions and requests them if not granted.
242+
*
243+
* This function identifies which of the required permissions (READ_CALL_LOG,
244+
* READ_PHONE_STATE, READ_CONTACTS, ANSWER_PHONE_CALLS, and POST_NOTIFICATIONS
245+
* for Android Tiramisu and above) are not granted.
246+
*
247+
* It then differentiates between permissions that haven't been requested yet
248+
* (missingPermissions) and those that have been denied by the user previously
249+
* (deniedPermissions).
250+
*
251+
* For missing permissions, it directly requests them using `ActivityCompat.requestPermissions`.
252+
* For denied permissions, it calls `showPermissionToastAndRequest` to inform the user
253+
* and guide them to settings.
254+
*/
239255
private fun checkPermissionsAndRequest() {
240256
val permissions = mutableListOf(
241257
Manifest.permission.READ_CALL_LOG,
@@ -273,7 +289,6 @@ class MainActivity : AppCompatActivity(), CallLogAdapter.OnItemChangedListener {
273289
*
274290
* @param missingPermissions a list of permissions that are missing.
275291
*/
276-
277292
private fun showPermissionToastAndRequest(missingPermissions: List<String>) {
278293
val permissionNames = missingPermissions.map { "" + getPermissionName(it) }
279294
val message =
@@ -300,6 +315,12 @@ class MainActivity : AppCompatActivity(), CallLogAdapter.OnItemChangedListener {
300315
permissionDeniedDialog?.show()
301316
}
302317

318+
/**
319+
* Returns the human-readable name for a given permission string.
320+
*
321+
* @param permission The permission string (e.g., Manifest.permission.READ_CALL_LOG).
322+
* @return The human-readable name of the permission, or the original permission string if no mapping is found.
323+
*/
303324
private fun getPermissionName(permission: String): String {
304325
return when (permission) {
305326
Manifest.permission.READ_CALL_LOG -> getString(R.string.permission_read_call_log)

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

Lines changed: 132 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.widget.Button
77
import android.widget.Toast
88
import androidx.activity.result.contract.ActivityResultContracts
99
import androidx.appcompat.app.AppCompatActivity
10+
import androidx.core.content.edit
1011
import androidx.preference.PreferenceFragmentCompat
1112
import org.json.JSONArray
1213
import org.json.JSONException
@@ -24,55 +25,119 @@ class SettingsActivity : AppCompatActivity() {
2425
val importButton: Button = findViewById(R.id.btn_import)
2526

2627
exportButton.setOnClickListener {
27-
exportFileLauncher.launch("spam_call_blocker_prefs.json")
28+
exportFileLauncher.launch("spam_call_blocker_export.json")
2829
}
2930

3031
importButton.setOnClickListener {
3132
importFileLauncher.launch(arrayOf("application/json"))
3233
}
3334
}
3435

36+
/**
37+
* Updates the settings container by replacing its content with a new instance of SettingsFragment.
38+
* This function is typically called when the activity is created or when settings need to be refreshed,
39+
* for example, after importing new settings.
40+
*/
3541
private fun updateSettingsContainer() {
3642
supportFragmentManager
3743
.beginTransaction()
3844
.replace(R.id.settings_container, SettingsFragment())
3945
.commit()
4046
}
4147

42-
// Fragmento para cargar las preferencias
48+
/**
49+
* A [PreferenceFragmentCompat] that displays the application's settings.
50+
* It loads preferences from an XML resource file.
51+
*/
4352
class SettingsFragment : PreferenceFragmentCompat() {
4453
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
4554
setPreferencesFromResource(R.xml.preferences, rootKey)
4655
}
4756
}
4857

58+
/**
59+
* ActivityResultLauncher for creating a document to export SharedPreferences.
60+
*
61+
* This launcher is used to initiate the system's file picker to select a location
62+
* and name for the JSON file where the SharedPreferences data will be exported.
63+
*
64+
* Upon successful selection of a URI by the user, the [exportAllSharedPreferences]
65+
* method is called with the chosen URI. A Toast message is displayed to indicate
66+
* the success or failure of the export operation.
67+
*/
4968
private val exportFileLauncher =
5069
registerForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri: Uri? ->
5170
uri?.let {
5271
if (exportAllSharedPreferences(it)) {
53-
Toast.makeText(this, "Preferencias exportadas con éxito", Toast.LENGTH_SHORT)
72+
Toast.makeText(
73+
this,
74+
this.getString(R.string.preferences_export_success),
75+
Toast.LENGTH_SHORT
76+
)
5477
.show()
5578
} else {
56-
Toast.makeText(this, "Error al exportar preferencias", Toast.LENGTH_SHORT)
79+
Toast.makeText(
80+
this,
81+
this.getString(R.string.preferences_export_error),
82+
Toast.LENGTH_SHORT
83+
)
5784
.show()
5885
}
5986
}
6087
}
6188

89+
/**
90+
* ActivityResultLauncher for handling the result of opening a document to import SharedPreferences.
91+
* It expects a URI as a result. If a URI is received, it attempts to import
92+
* SharedPreferences from the selected file.
93+
* Displays a success or error Toast message based on the outcome of the import operation.
94+
* After a successful import, it updates the settings container.
95+
*/
6296
private val importFileLauncher =
6397
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
6498
uri?.let {
6599
if (importAllSharedPreferences(it)) {
66100
updateSettingsContainer()
67-
Toast.makeText(this, "Preferencias importadas con éxito", Toast.LENGTH_SHORT)
101+
Toast.makeText(
102+
this,
103+
this.getString(R.string.preferences_import_success),
104+
Toast.LENGTH_SHORT
105+
)
68106
.show()
69107
} else {
70-
Toast.makeText(this, "Error al importar preferencias", Toast.LENGTH_SHORT)
108+
Toast.makeText(
109+
this,
110+
this.getString(R.string.preferences_import_error),
111+
Toast.LENGTH_SHORT
112+
)
71113
.show()
72114
}
73115
}
74116
}
75117

118+
/**
119+
* Exports all SharedPreferences to a JSON file.
120+
*
121+
* This function iterates through all SharedPreferences files in the application's
122+
* `shared_prefs` directory. For each file, it reads all key-value pairs
123+
* and constructs a JSON object representing the preferences. These individual
124+
* JSON objects are then combined into a single parent JSON object, where each
125+
* key is the name of the SharedPreferences file (without the .xml extension)
126+
* and the value is the corresponding preferences JSON object.
127+
*
128+
* The resulting parent JSON object is then written to the `OutputStream`
129+
* associated with the provided `Uri`.
130+
*
131+
* Special handling is implemented for `Set<String>` values, which are
132+
* converted to JSONArrays. Other primitive types (Boolean, Int, Long, Float, String)
133+
* are directly put into the JSON object.
134+
*
135+
* @param uri The Uri of the file where the SharedPreferences data will be exported.
136+
* This Uri should be obtained from a file picker (e.g., using
137+
* `ActivityResultContracts.CreateDocument`).
138+
* @return `true` if the export was successful, `false` otherwise (e.g., if an
139+
* IOException or JSONException occurs).
140+
*/
76141
private fun exportAllSharedPreferences(uri: Uri): Boolean {
77142
return try {
78143
val prefsDir = File(applicationInfo.dataDir, "shared_prefs")
@@ -109,6 +174,21 @@ class SettingsActivity : AppCompatActivity() {
109174
}
110175
}
111176

177+
/**
178+
* Imports all SharedPreferences from a JSON file specified by the given URI.
179+
*
180+
* This function reads a JSON file, validates its structure, and then iterates through
181+
* each SharedPreferences file represented in the JSON. For each preference file,
182+
* it reads the key-value pairs and writes them to the corresponding SharedPreferences
183+
* on the device.
184+
*
185+
* Supported data types for import are: `String`, `Int`, `Long`, `Float`, `Boolean`,
186+
* and `Set<String>` (represented as a JSONArray of strings in the JSON).
187+
*
188+
* @param uri The URI of the JSON file to import SharedPreferences from.
189+
* @return `true` if the import was successful, `false` otherwise (e.g., if the file is invalid,
190+
* an I/O error occurs, or a JSON parsing error occurs).
191+
*/
112192
private fun importAllSharedPreferences(uri: Uri): Boolean {
113193
return try {
114194
contentResolver.openInputStream(uri)?.use { inputStream ->
@@ -117,35 +197,37 @@ class SettingsActivity : AppCompatActivity() {
117197

118198
// Validar el archivo JSON antes de importar
119199
if (!isValidSharedPreferencesJson(jsonObject)) {
120-
Toast.makeText(this, "El archivo JSON no es válido", Toast.LENGTH_SHORT).show()
200+
Toast.makeText(
201+
this,
202+
this.getString(R.string.invalid_json_file),
203+
Toast.LENGTH_SHORT
204+
).show()
121205
return false
122206
}
123207

124208
for (prefName in jsonObject.keys()) {
125209
val sharedPreferences = getSharedPreferences(prefName, Context.MODE_PRIVATE)
126-
val editor = sharedPreferences.edit()
127-
128-
val prefJsonObject = jsonObject.getJSONObject(prefName)
129-
for (key in prefJsonObject.keys()) {
130-
val value = prefJsonObject.get(key)
131-
when (value) {
132-
is JSONArray -> {
133-
val set = mutableSetOf<String>()
134-
for (i in 0 until value.length()) {
135-
set.add(value.getString(i))
210+
sharedPreferences.edit {
211+
212+
val prefJsonObject = jsonObject.getJSONObject(prefName)
213+
for (key in prefJsonObject.keys()) {
214+
when (val value = prefJsonObject.get(key)) {
215+
is JSONArray -> {
216+
val set = mutableSetOf<String>()
217+
for (i in 0 until value.length()) {
218+
set.add(value.getString(i))
219+
}
220+
putStringSet(key, set)
136221
}
137-
editor.putStringSet(key, set)
138-
}
139222

140-
is Int -> editor.putInt(key, value)
141-
is Long -> editor.putLong(key, value)
142-
is Float -> editor.putFloat(key, value)
143-
is Boolean -> editor.putBoolean(key, value)
144-
is String -> editor.putString(key, value)
145-
else -> throw IllegalArgumentException("Tipo no soportado")
223+
is Int -> putInt(key, value)
224+
is Long -> putLong(key, value)
225+
is Float -> putFloat(key, value)
226+
is Boolean -> putBoolean(key, value)
227+
is String -> putString(key, value)
228+
}
146229
}
147230
}
148-
editor.apply()
149231
}
150232
}
151233
true
@@ -155,6 +237,30 @@ class SettingsActivity : AppCompatActivity() {
155237
}
156238
}
157239

240+
/**
241+
* Validates if the given JSONObject represents a valid SharedPreferences structure.
242+
*
243+
* This function checks if the JSON object conforms to the expected format of
244+
* SharedPreferences data. It iterates through each preference file (represented by
245+
* a key in the root JSONObject) and then iterates through each key-value pair
246+
* within that preference file.
247+
*
248+
* The validation ensures:
249+
* - Each preference file is a JSONObject.
250+
* - Values within each preference file are of supported types:
251+
* - Int
252+
* - Long
253+
* - Float
254+
* - Boolean
255+
* - String
256+
* - JSONArray (where all elements are Strings, representing a Set<String>)
257+
*
258+
* If any part of the JSON structure does not conform to these rules, or if a
259+
* JSONException occurs during parsing, the function returns false.
260+
*
261+
* @param jsonObject The JSONObject to validate.
262+
* @return True if the JSONObject is a valid representation of SharedPreferences, false otherwise.
263+
*/
158264
private fun isValidSharedPreferencesJson(jsonObject: JSONObject): Boolean {
159265
return try {
160266
for (prefName in jsonObject.keys()) {

app/src/main/java/com/addev/listaspam/util/ApiUtils.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ object ApiUtils {
7676
* @param phone The phone number being reported.
7777
* @param comment The user comment about the phone number.
7878
* @param lang The language code (e.g., "ES" for Spanish).
79-
* @param username Optional: The username of the person submitting the report.
80-
* @param phoneOwner Optional: A string indicating ownership or recipient identity.
8179
* @return `true` if the report was submitted successfully; `false` otherwise.
8280
*/
8381
fun reportToUnknownPhone(
@@ -89,7 +87,7 @@ object ApiUtils {
8987
val optRating = if (isSpam) "1" else "5"
9088

9189
val formBuilder = FormBody.Builder()
92-
.add("api_key", "d58d5bdaba8a80b2311957e9e4af885c")
90+
.add("api_key", UNKNOWN_PHONE_API_KEY)
9391
.add("phone", phone)
9492
.add("_action", "_submit_comment")
9593
.add("comment", comment)

0 commit comments

Comments
 (0)