Skip to content

Commit de9242a

Browse files
committed
Add internationalization and copy number to clipboard feature
1 parent 3396398 commit de9242a

File tree

5 files changed

+106
-83
lines changed

5 files changed

+106
-83
lines changed

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

Lines changed: 39 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,23 @@ import com.addev.listaspam.calllog.CallLogAdapter
2727
import com.addev.listaspam.calllog.getCallLogs
2828
import com.addev.listaspam.utils.SpamUtils
2929

30-
/**
31-
* MainActivity for handling permissions and requesting call screening role.
32-
* Requires Android P (API level 28) or higher.
33-
*
34-
* Note: For Android Q (API level 29) or higher, MyCallScreeningService should be used instead.
35-
*/
3630
class MainActivity : AppCompatActivity() {
3731

3832
private lateinit var intentLauncher: ActivityResultLauncher<Intent>
33+
private var permissionDeniedDialog: AlertDialog? = null
34+
3935

40-
/**
41-
* Called when the activity is starting.
42-
* @param savedInstanceState If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). Otherwise it is null.
43-
*/
4436
override fun onCreate(savedInstanceState: Bundle?) {
4537
super.onCreate(savedInstanceState)
4638
setContentView(R.layout.activity_main)
4739
setupWindowInsets()
4840
setupIntentLauncher()
4941
}
5042

51-
/**
52-
* Initializes the activity by checking permissions, requesting the call screening role, and refreshing the call logs if the necessary permissions are granted.
53-
*/
5443
private fun init() {
5544
checkPermissionsAndRequest()
5645

57-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
58-
requestCallScreeningRole()
59-
}
46+
requestCallScreeningRole()
6047

6148
if (ContextCompat.checkSelfPermission(
6249
this,
@@ -67,9 +54,6 @@ class MainActivity : AppCompatActivity() {
6754
}
6855
}
6956

70-
/**
71-
* Called when the activity comes to the foreground.
72-
*/
7357
override fun onResume() {
7458
super.onResume()
7559
init()
@@ -89,9 +73,6 @@ class MainActivity : AppCompatActivity() {
8973
return sharedPreferences.getStringSet(SpamUtils.BLOCK_NUMBERS_KEY, emptySet()) ?: emptySet()
9074
}
9175

92-
/**
93-
* Sets up the window insets to ensure proper padding for system bars.
94-
*/
9576
private fun setupWindowInsets() {
9677
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
9778
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
@@ -100,33 +81,21 @@ class MainActivity : AppCompatActivity() {
10081
}
10182
}
10283

103-
/**
104-
* Sets up the ActivityResultLauncher for handling the result of requesting the call screening role.
105-
*/
10684
private fun setupIntentLauncher() {
10785
intentLauncher =
10886
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
10987
if (it.resultCode == Activity.RESULT_OK) {
110-
showToast(this, "Success requesting ROLE_CALL_SCREENING!")
88+
showToast(this, getString(R.string.success_call_screening_role))
11189
} else {
112-
showToast(this, "Failed requesting ROLE_CALL_SCREENING")
90+
showToast(this, getString(R.string.failed_call_screening_role))
11391
}
11492
}
11593
}
11694

117-
/**
118-
* Shows a toast message.
119-
* @param context Context for accessing resources.
120-
* @param message The message to display in the toast.
121-
* @param duration The length of time to show the toast. Default is Toast.LENGTH_SHORT.
122-
*/
12395
private fun showToast(context: Context, message: String, duration: Int = Toast.LENGTH_SHORT) {
12496
Toast.makeText(context, message, duration).show()
12597
}
12698

127-
/**
128-
* Checks and requests the necessary permissions.
129-
*/
13099
private fun checkPermissionsAndRequest() {
131100
val permissions = mutableListOf(
132101
Manifest.permission.READ_CALL_LOG,
@@ -145,46 +114,61 @@ class MainActivity : AppCompatActivity() {
145114
}
146115
}
147116

117+
/**
118+
* Shows a dialog to the user when permissions are required but not granted, and requests the missing permissions.
119+
* If the dialog is already visible, it will not be shown again.
120+
*
121+
* @param missingPermissions a list of permissions that are missing.
122+
*/
123+
148124
private fun showPermissionToastAndRequest(missingPermissions: List<String>) {
149125
val permissionNames = missingPermissions.map { "- " + getPermissionName(it) }
150-
val message =
151-
"Los siguientes permisos son necesarios para que la aplicación funcione correctamente:\n\n${
152-
permissionNames.joinToString("\n")
153-
}\n\nPor favor, ve a los ajustes del sistema y otorga los permisos manualmente."
154-
AlertDialog.Builder(this)
155-
.setTitle("Permisos requeridos")
126+
val message = getString(R.string.permissions_required_message, permissionNames.joinToString("\n"))
127+
128+
if (permissionDeniedDialog?.isShowing == true) {
129+
return
130+
}
131+
132+
permissionDeniedDialog = AlertDialog.Builder(this)
133+
.setTitle(R.string.permissions_required_title)
156134
.setMessage(message)
157-
.setPositiveButton("Ir a ajustes") { _, _ ->
135+
.setPositiveButton(R.string.go_to_settings) { _, _ ->
158136
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
159137
intent.data = Uri.fromParts("package", packageName, null)
160138
startActivity(intent)
161139
}
162-
.show()
140+
.setOnDismissListener {
141+
permissionDeniedDialog = null
142+
}
143+
.create()
144+
permissionDeniedDialog?.show()
163145
}
164146

165147
private fun getPermissionName(permission: String): String {
166148
return when (permission) {
167-
Manifest.permission.READ_CALL_LOG -> "Acceso al registro de llamadas"
168-
Manifest.permission.READ_PHONE_STATE -> "Acceso al estado del teléfono"
169-
Manifest.permission.READ_CONTACTS -> "Acceso a los contactos"
170-
Manifest.permission.ANSWER_PHONE_CALLS -> "Responder llamadas telefónicas"
171-
Manifest.permission.POST_NOTIFICATIONS -> "Notificaciones"
149+
Manifest.permission.READ_CALL_LOG -> getString(R.string.permission_read_call_log)
150+
Manifest.permission.READ_PHONE_STATE -> getString(R.string.permission_read_phone_state)
151+
Manifest.permission.READ_CONTACTS -> getString(R.string.permission_read_contacts)
152+
Manifest.permission.ANSWER_PHONE_CALLS -> getString(R.string.permission_answer_phone_calls)
153+
Manifest.permission.POST_NOTIFICATIONS -> getString(R.string.permission_post_notifications)
172154
else -> permission
173155
}
174156
}
175157

176158
/**
177159
* Requests the call screening role.
178160
*/
179-
@RequiresApi(Build.VERSION_CODES.Q)
180161
private fun requestCallScreeningRole() {
181-
val telecomManager = ContextCompat.getSystemService(this, TelecomManager::class.java)
162+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
163+
requestRoleForQAndAbove()
164+
} else {
165+
val telecomManager = ContextCompat.getSystemService(this, TelecomManager::class.java)
182166

183-
if (telecomManager == null) {
184-
showToast(this, getString(R.string.telecom_manager_unavailable))
185-
return
167+
if (telecomManager == null) {
168+
showToast(this, getString(R.string.telecom_manager_unavailable))
169+
return
170+
}
186171
}
187-
requestRoleForQAndAbove()
188172
}
189173

190174
/**
@@ -199,20 +183,4 @@ class MainActivity : AppCompatActivity() {
199183
showToast(this, getString(R.string.call_screening_role_prompt), Toast.LENGTH_LONG)
200184
}
201185
}
202-
203-
/*
204-
/**
205-
* Requests the call screening role for Android P (API level 28) and below.
206-
* @param telecomManager The TelecomManager for managing telecom services.
207-
*/
208-
private fun requestRoleForBelowQ(telecomManager: TelecomManager) {
209-
if (telecomManager.defaultDialerPackage == packageName) {
210-
showToast(this, getString(R.string.call_screening_role_already_granted))
211-
} else {
212-
val intent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
213-
startActivity(intent)
214-
showToast(this, getString(R.string.call_screening_role_prompt), Toast.LENGTH_LONG)
215-
}
216-
}
217-
*/
218186
}

app/src/main/java/com/addev/listaspam/calllog/CallLogAdapter.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import android.view.View
88
import android.view.ViewGroup
99
import android.widget.Button
1010
import android.widget.TextView
11+
import android.widget.Toast
1112
import androidx.core.content.ContextCompat
1213
import androidx.recyclerview.widget.RecyclerView
1314
import com.addev.listaspam.R
@@ -43,22 +44,36 @@ class CallLogAdapter(
4344

4445
fun bind(callLog: CallLogEntry, isBlocked: Boolean) {
4546
val number = callLog.number ?: ""
46-
val textToShow = if (isBlocked) "$number (blocked)" else number
47+
val textToShow = if (isBlocked) {
48+
context.getString(R.string.blocked_text_format, number)
49+
} else {
50+
number
51+
}
4752
numberTextView.text = textToShow
4853
dateTextView.text = formatter.format(callLog.date)
49-
durationTextView.text = "Duration: ${callLog.duration} seconds"
54+
durationTextView.text = context.getString(R.string.duration_label, callLog.duration)
5055

5156
if (isBlocked) {
5257
numberTextView.setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_light))
5358
} else {
5459
numberTextView.setTextColor(ContextCompat.getColor(context, android.R.color.system_palette_key_color_neutral_light)) // O el color por defecto
5560
}
5661

62+
// Open ListaSpam reporting form in browser
5763
reportButton.setOnClickListener {
5864
val url = String.format(SpamUtils.REPORT_URL_TEMPLATE, number)
5965
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
6066
context.startActivity(intent)
6167
}
68+
69+
// Copy number to clipboard
70+
itemView.setOnLongClickListener {
71+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
72+
val clip = android.content.ClipData.newPlainText("phone number", number)
73+
clipboard.setPrimaryClip(clip)
74+
Toast.makeText(context, context.getString(R.string.number_copied_to_clipboard), Toast.LENGTH_SHORT).show()
75+
true
76+
}
6277
}
6378
}
6479
}

app/src/main/java/com/addev/listaspam/utils/SpamUtils.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ class SpamUtils {
221221
callback: (isSpam: Boolean) -> Unit
222222
) {
223223
Handler(Looper.getMainLooper()).post {
224-
showToast(context, "Incoming call is not spam", Toast.LENGTH_LONG)
224+
showToast(context, context.getString(R.string.incoming_call_not_spam), Toast.LENGTH_LONG)
225225
}
226226
removeSpamNumber(context, number)
227227
callback(false)
@@ -278,10 +278,10 @@ class SpamUtils {
278278
*/
279279
private fun sendNotification(context: Context, number: String) {
280280
createNotificationChannel(context)
281-
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
281+
val notification = NotificationCompat.Builder(context, context.getString(R.string.notification_channel_id))
282282
.setSmallIcon(R.mipmap.ic_launcher)
283-
.setContentTitle("Spam Number Blocked")
284-
.setContentText("Blocked spam number: $number")
283+
.setContentTitle(context.getString(R.string.notification_title_spam_blocked))
284+
.setContentText(context.getString(R.string.notification_text_spam_blocked, number))
285285
.setPriority(NotificationCompat.PRIORITY_HIGH)
286286
.build()
287287

@@ -301,8 +301,8 @@ class SpamUtils {
301301
* @param context Context for creating the notification channel.
302302
*/
303303
private fun createNotificationChannel(context: Context) {
304-
val name = "Spam Blocker Channel"
305-
val descriptionText = "Notifications for blocked spam numbers"
304+
val name = context.getString(R.string.notification_channel_id)
305+
val descriptionText = context.getString(R.string.notification_channel_id)
306306
val importance = NotificationManager.IMPORTANCE_HIGH
307307
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance).apply {
308308
description = descriptionText
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<resources>
2+
<string name="app_name">ListaSpam</string>
3+
<string name="success_call_screening_role">¡Éxito al solicitar el rol de filtrado de llamadas!</string>
4+
<string name="failed_call_screening_role">Error al solicitar el rol de filtrado de llamadas</string>
5+
<string name="telecom_manager_unavailable">El Administrador de Telecomunicaciones no está disponible.</string>
6+
<string name="call_screening_role_prompt">Por favor, otorga el rol de filtrado de llamadas.</string>
7+
<string name="permissions_required_title">Permisos Requeridos</string>
8+
<string name="permissions_required_message">Los siguientes permisos son necesarios para que la aplicación funcione correctamente:\n\n%s\n\nPor favor, ve a los ajustes del sistema y otorga los permisos manualmente.</string>
9+
<string name="go_to_settings">Ir a ajustes</string>
10+
<string name="permission_read_call_log">Acceso al registro de llamadas</string>
11+
<string name="permission_read_phone_state">Acceso al estado del teléfono</string>
12+
<string name="permission_read_contacts">Acceso a los contactos</string>
13+
<string name="permission_answer_phone_calls">Responder llamadas telefónicas</string>
14+
<string name="permission_post_notifications">Notificaciones</string>
15+
<string name="blocked_text_format">%1$s (bloqueado)</string>
16+
<string name="duration_label">Duración: %1$d segundos</string>
17+
<string name="report_url_template">http://example.com/reportar?number=%1$s</string>
18+
<string name="incoming_call_not_spam">La llamada entrante no es spam</string>
19+
<string name="notification_channel_id">ListaSpam</string>
20+
<string name="notification_title_spam_blocked">Número de spam bloqueado</string>
21+
<string name="notification_text_spam_blocked">Número de spam bloqueado: %1$s</string>
22+
<string name="number_copied_to_clipboard">Número copiado al portapapeles</string>
23+
</resources>
Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
<resources>
22
<string name="app_name">ListaSpam</string>
3-
<string name="call_screening_role_prompt">Please grant Call Screening role to this app.</string>
4-
<string name="telecom_manager_unavailable">TelecomManager not available.</string>
5-
<string name="role_manager_unavailable">RoleManager is not available on this device.</string>
3+
<string name="success_call_screening_role">Success requesting role call screening!</string>
4+
<string name="failed_call_screening_role">Failed requesting role call screening</string>
5+
<string name="telecom_manager_unavailable">Telecom Manager is unavailable.</string>
6+
<string name="call_screening_role_prompt">Please grant the call screening role.</string>
7+
<string name="permissions_required_title">Permissions Required</string>
8+
<string name="permissions_required_message">The following permissions are required for the app to function properly:\n\n%s\n\nPlease go to system settings and grant the permissions manually.</string>
9+
<string name="go_to_settings">Go to settings</string>
10+
<string name="permission_read_call_log">Read Call Log</string>
11+
<string name="permission_read_phone_state">Read Phone State</string>
12+
<string name="permission_read_contacts">Read Contacts</string>
13+
<string name="permission_answer_phone_calls">Answer Phone Calls</string>
14+
<string name="permission_post_notifications">Post Notifications</string>
15+
<string name="blocked_text_format">%1$s (blocked)</string>
16+
<string name="duration_label">Duration: %1$d seconds</string>
17+
<string name="report_url_template">http://example.com/report?number=%1$s</string>
18+
<string name="incoming_call_not_spam">Incoming call is not spam</string>
19+
<string name="notification_channel_id">ListaSpam</string>
20+
<string name="notification_title_spam_blocked">Spam Number Blocked</string>
21+
<string name="notification_text_spam_blocked">Blocked spam number: %1$s</string>
22+
<string name="number_copied_to_clipboard">Phone number copied to clipboard</string>
623

7-
</resources>
24+
</resources>

0 commit comments

Comments
 (0)