Skip to content

Commit 159ad05

Browse files
jhonsu01claude
andcommitted
feat: sincronizar antes de aportar (sin duplicados) + Ko-fi + enlace al repo (v1.3.0)
- mergeRemote marca como 'github' los numeros locales que ya estan en la base publica, para que el aporte solo envie numeros nuevos (menos Issues duplicados). - Lista SPAM: boton "Sincronizar base publica" debajo de Aportar + nota. - Proteccion: boton de donacion Ko-fi y pie con enlace al repositorio en GitHub. - versionName 1.3.0 / versionCode 6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3a35b59 commit 159ad05

4 files changed

Lines changed: 93 additions & 5 deletions

File tree

‎RELEASE_NOTES_v1.3.0.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# OpenCallShield v1.3.0
2+
3+
Aportes más limpios (sin duplicados), donaciones y enlace al proyecto.
4+
5+
## Novedades
6+
7+
- 🔄 **Sincronizar antes de aportar:** nuevo botón **"Sincronizar base pública"** en
8+
*Lista SPAM*, justo debajo de *Aportar*, con una nota que recomienda sincronizar primero.
9+
- 🚫 **Sin reportes duplicados:** al sincronizar, los números del usuario que ya están en
10+
la base pública se marcan automáticamente, y **el aporte solo envía los números nuevos**.
11+
Esto reduce los Issues con números repetidos.
12+
- ☕ **Donaciones (Ko-fi):** botón de apoyo en la pestaña *Protección* (debajo de
13+
"Sincronizar ahora") que abre la página de Ko-fi del proyecto.
14+
- 🔗 **Enlace al proyecto:** pie **"OpenCallShield – ver el proyecto en GitHub"** al final
15+
de *Protección*, que abre el repositorio.
16+
17+
## Técnico
18+
19+
- `mergeRemote` marca como `github` los números locales que también están en la base
20+
pública (evita re-aportarlos). versionName `1.3.0` (versionCode 6).

‎app/build.gradle.kts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ android {
2626
applicationId = "io.github.jhonsu01.opencallshield"
2727
minSdk = 29
2828
targetSdk = 34
29-
versionCode = 5
30-
versionName = "1.2.1"
29+
versionCode = 6
30+
versionName = "1.3.0"
3131
vectorDrawables { useSupportLibrary = true }
3232
}
3333

‎app/src/main/java/com/opencallshield/data/SpamRepository.kt‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,24 @@ class SpamRepository(
3434

3535
suspend fun remove(number: String) = dao.deleteByNumber(normalize(number))
3636

37-
/** Mezcla la lista remota con la base local sin sobrescribir reportes locales. */
37+
/**
38+
* Mezcla la base publica con la local. Si un numero que el usuario ya tenia
39+
* reportado (source "local") tambien aparece en la base publica, se marca como
40+
* "github" para NO volver a aportarlo: asi solo se reportan numeros nuevos y se
41+
* evitan Issues con duplicados.
42+
*/
3843
suspend fun mergeRemote(remote: List<SpamNumber>) {
3944
if (remote.isEmpty()) return
40-
dao.insertAll(remote.map { it.copy(number = normalize(it.number)) })
45+
for (item in remote) {
46+
val n = normalize(item.number)
47+
if (n.isEmpty()) continue
48+
val existing = dao.findByNumber(n)
49+
if (existing == null) {
50+
dao.upsert(item.copy(number = n, source = "github"))
51+
} else if (existing.source != "github") {
52+
dao.upsert(existing.copy(source = "github"))
53+
}
54+
}
4155
}
4256

4357
suspend fun logBlocked(number: String, reason: String, silenced: Boolean) {

‎app/src/main/java/com/opencallshield/ui/MainScreen.kt‎

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ import androidx.compose.material.icons.filled.CloudUpload
2525
import androidx.compose.material.icons.filled.Delete
2626
import androidx.compose.material.icons.filled.History
2727
import androidx.compose.material.icons.filled.Shield
28+
import androidx.compose.material.icons.filled.Sync
2829
import androidx.compose.material3.Button
30+
import androidx.compose.material3.ButtonDefaults
2931
import androidx.compose.material3.Card
3032
import androidx.compose.material3.CardDefaults
3133
import androidx.compose.material3.CircularProgressIndicator
@@ -43,6 +45,7 @@ import androidx.compose.material3.SnackbarHostState
4345
import androidx.compose.material3.Switch
4446
import androidx.compose.material3.Tab
4547
import androidx.compose.material3.Text
48+
import androidx.compose.material3.TextButton
4649
import androidx.compose.material3.TopAppBar
4750
import androidx.compose.runtime.Composable
4851
import androidx.compose.runtime.LaunchedEffect
@@ -53,6 +56,7 @@ import androidx.compose.runtime.remember
5356
import androidx.compose.runtime.setValue
5457
import androidx.compose.ui.Alignment
5558
import androidx.compose.ui.Modifier
59+
import androidx.compose.ui.graphics.Color
5660
import androidx.compose.ui.platform.LocalUriHandler
5761
import androidx.compose.ui.text.style.TextOverflow
5862
import androidx.compose.ui.unit.dp
@@ -64,6 +68,9 @@ import java.text.SimpleDateFormat
6468
import java.util.Date
6569
import java.util.Locale
6670

71+
private const val REPO_URL = "https://github.com/jhonsu01/OpenCallShield"
72+
private const val KOFI_URL = "https://ko-fi.com/V7V81LV7GX"
73+
6774
@OptIn(ExperimentalMaterial3Api::class)
6875
@Composable
6976
fun MainScreen(
@@ -119,7 +126,7 @@ fun MainScreen(
119126
}
120127
when (selectedTab) {
121128
0 -> ProtectionTab(state, viewModel, onRequestRole, spamNumbers.size, blockedCalls.size)
122-
1 -> SpamListTab(spamNumbers, authState, viewModel)
129+
1 -> SpamListTab(spamNumbers, authState, state.syncing, viewModel)
123130
2 -> HistoryTab(blockedCalls, spamNumbers, viewModel)
124131
else -> AccountTab(authState, viewModel)
125132
}
@@ -135,6 +142,7 @@ private fun ProtectionTab(
135142
spamCount: Int,
136143
blockedCount: Int
137144
) {
145+
val uriHandler = LocalUriHandler.current
138146
Column(
139147
Modifier
140148
.fillMaxSize()
@@ -220,6 +228,29 @@ private fun ProtectionTab(
220228
Text("Sincronizar ahora")
221229
}
222230
}
231+
232+
HorizontalDivider()
233+
234+
// Donaciones (Ko-fi)
235+
Button(
236+
onClick = { uriHandler.openUri(KOFI_URL) },
237+
modifier = Modifier.fillMaxWidth(),
238+
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF29ABE0))
239+
) {
240+
Text("☕ Apoyame en Ko-fi")
241+
}
242+
Text(
243+
"OpenCallShield es gratuito y open source. Si te resulta util, considera apoyarlo.",
244+
style = MaterialTheme.typography.bodySmall
245+
)
246+
247+
// Pie: enlace al repositorio
248+
TextButton(
249+
onClick = { uriHandler.openUri(REPO_URL) },
250+
modifier = Modifier.fillMaxWidth()
251+
) {
252+
Text("OpenCallShield - ver el proyecto en GitHub")
253+
}
223254
}
224255
}
225256

@@ -246,6 +277,7 @@ private fun SettingRow(
246277
private fun SpamListTab(
247278
numbers: List<SpamNumber>,
248279
authState: AuthUiState,
280+
syncing: Boolean,
249281
viewModel: MainViewModel
250282
) {
251283
var input by remember { mutableStateOf("") }
@@ -283,6 +315,28 @@ private fun SpamListTab(
283315
)
284316
}
285317

318+
Spacer(Modifier.size(8.dp))
319+
OutlinedButton(
320+
onClick = { viewModel.syncNow() },
321+
modifier = Modifier.fillMaxWidth(),
322+
enabled = !syncing
323+
) {
324+
if (syncing) {
325+
CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp)
326+
Spacer(Modifier.width(8.dp))
327+
Text("Sincronizando...")
328+
} else {
329+
Icon(Icons.Filled.Sync, contentDescription = null)
330+
Spacer(Modifier.width(8.dp))
331+
Text("Sincronizar base publica")
332+
}
333+
}
334+
Text(
335+
"Sincroniza primero la base publica: los numeros que ya esten en ella no se " +
336+
"vuelven a reportar. Asi solo aportas numeros nuevos y evitas duplicados.",
337+
style = MaterialTheme.typography.bodySmall
338+
)
339+
286340
Spacer(Modifier.size(12.dp))
287341
if (numbers.isEmpty()) {
288342
EmptyState("Aun no hay numeros reportados.")

0 commit comments

Comments
 (0)