Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 19 additions & 29 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,41 +1,31 @@
# Gradle
.gradle/
build/

# Android Studio / IntelliJ
# Gradle
# Keys / services
# Local config
local.properties

# Logs & profiling
# NDK / C++
# OS
# Output
# Sesiones PBL (Learning/Aprendizaje)
docs/
*.aab
*.apk
output-metadata.json

# Android Studio / IntelliJ
*.iml
.idea/

# NDK / C++
build/
captures/
.externalNativeBuild/
.cxx/

# Keys / services
.DS_Store
.externalNativeBuild/
google-services.json
.gradle/
*.hprof
.idea/
*.iml
*.jks
*.keystore
keystore.properties
google-services.json

# Logs & profiling
local.properties
*.log
*.hprof

# OS
.DS_Store
Thumbs.db

*.keystore

# Sesiones PBL (Learning/Aprendizaje)
markdown/


output-metadata.json
Thumbs.db
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ android {
applicationId = "com.d4vram.cbdcounter"
minSdk = 26
targetSdk = 35
versionCode = 6
versionName = "1.1.0"
versionCode = 7
versionName = "1.4.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
Binary file modified app/release/baselineProfiles/0/app-release.dm
Binary file not shown.
Binary file modified app/release/baselineProfiles/1/app-release.dm
Binary file not shown.
15 changes: 12 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@
tools:targetApi="31">

<activity
android:name=".StatsActivity"
android:name=".CalendarActivity"
android:exported="false"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>

<activity
android:name=".DashboardActivity"
android:exported="false"
android:parentActivityName=".MainActivity">
<meta-data
Expand Down Expand Up @@ -46,10 +55,10 @@
<activity
android:name=".EvolutionActivity"
android:exported="false"
android:parentActivityName=".StatsActivity">
android:parentActivityName=".CalendarActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".StatsActivity" />
android:value=".CalendarActivity" />
</activity>

<activity
Expand Down
176 changes: 176 additions & 0 deletions app/src/main/java/com/d4vram/cbdcounter/BackupManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package com.d4vram.cbdcounter

import android.content.Context
import android.net.Uri
import android.util.Log
import org.json.JSONObject
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

object BackupManager {

private const val TAG = "BackupManager"
private const val PREFS_NAME = "CBDCounter"
private const val EMOJI_PREFS_NAME = "emoji_prefs"
private const val JSON_FILE_NAME = "data.json"

// Estructura del JSON de backup
// {
// "version": 1,
// "timestamp": 1234567890,
// "prefs": { ... },
// "emoji_prefs": { ... }
// }

fun createBackup(context: Context, destinationUri: Uri? = null): File? {
try {
// 1. Recopilar datos
val mainPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).all
val emojiPrefs = context.getSharedPreferences(EMOJI_PREFS_NAME, Context.MODE_PRIVATE).all

val backupJson = JSONObject()
backupJson.put("version", 1)
backupJson.put("timestamp", System.currentTimeMillis())

// Convertir mapas a JSONObjects
val prefsJson = JSONObject()
mainPrefs.forEach { (k, v) -> prefsJson.put(k, v) }
backupJson.put("prefs", prefsJson)

val emojiJson = JSONObject()
emojiPrefs.forEach { (k, v) -> emojiJson.put(k, v) }
backupJson.put("emoji_prefs", emojiJson)

// 2. Crear archivo temporal para el JSON
val tempDir = File(context.cacheDir, "backup_temp")
if (tempDir.exists()) tempDir.deleteRecursively()
tempDir.mkdirs()

val jsonFile = File(tempDir, JSON_FILE_NAME)
jsonFile.writeText(backupJson.toString(2))

// 3. Crear archivo ZIP de destino
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val zipFileName = "cbd_backup_$timeStamp.zip"
val zipFile = File(context.getExternalFilesDir(null), "backups/$zipFileName")
zipFile.parentFile?.mkdirs()

// 4. Escribir ZIP (incluyendo JSON y carpeta de audios)
ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use { zos ->
// Agregar JSON
addToZip(zos, jsonFile, JSON_FILE_NAME)

// Agregar Audios
val audioDir = File(context.filesDir, "audios")
if (audioDir.exists() && audioDir.isDirectory) {
audioDir.listFiles()?.forEach { audioFile ->
addToZip(zos, audioFile, "audios/${audioFile.name}")
}
}
}

// Limpiar
tempDir.deleteRecursively()

return zipFile

} catch (e: Exception) {
Log.e(TAG, "Error creating backup", e)
return null
}
}

private fun addToZip(zos: ZipOutputStream, file: File, entryName: String) {
if (!file.exists()) return
val entry = ZipEntry(entryName)
zos.putNextEntry(entry)
file.inputStream().use { it.copyTo(zos) }
zos.closeEntry()
}

fun restoreBackup(context: Context, uri: Uri): Boolean {
val tempDir = File(context.cacheDir, "restore_temp")
if (tempDir.exists()) tempDir.deleteRecursively()
tempDir.mkdirs()

try {
// 1. Descomprimir ZIP
context.contentResolver.openInputStream(uri)?.use { inputStream ->
java.util.zip.ZipInputStream(inputStream).use { zis ->
var entry = zis.nextEntry
while (entry != null) {
val file = File(tempDir, entry.name)
if (entry.isDirectory) {
file.mkdirs()
} else {
file.parentFile?.mkdirs()
file.outputStream().use { fos -> zis.copyTo(fos) }
}
entry = zis.nextEntry
}
}
}

// 2. Leer y validar JSON
val jsonFile = File(tempDir, JSON_FILE_NAME)
if (!jsonFile.exists()) return false

val jsonContent = jsonFile.readText()
val backupJson = JSONObject(jsonContent)

// 3. Restaurar Prefs (Main)
val prefsJson = backupJson.optJSONObject("prefs")
if (prefsJson != null) {
val editor = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit()
editor.clear() // Opcional: limpiar antes de restaurar
for (key in prefsJson.keys()) {
val value = prefsJson.get(key)
when (value) {
is Boolean -> editor.putBoolean(key, value)
is Int -> editor.putInt(key, value)
is Long -> editor.putLong(key, value)
is Float -> editor.putFloat(key, value.toFloat())
is String -> editor.putString(key, value)
}
}
editor.apply()
}

// 4. Restaurar Prefs (Emojis)
val emojiJson = backupJson.optJSONObject("emoji_prefs")
if (emojiJson != null) {
val editor = context.getSharedPreferences(EMOJI_PREFS_NAME, Context.MODE_PRIVATE).edit()
editor.clear()
for (key in emojiJson.keys()) {
val value = emojiJson.get(key)
if (value is String) editor.putString(key, value)
}
editor.apply()
}

// 5. Restaurar Audios
val audiosDir = File(tempDir, "audios")
if (audiosDir.exists() && audiosDir.isDirectory) {
val targetDir = File(context.filesDir, "audios")
targetDir.mkdirs()
audiosDir.listFiles()?.forEach { audioFile ->
audioFile.copyTo(File(targetDir, audioFile.name), overwrite = true)
}
}

return true

} catch (e: Exception) {
Log.e(TAG, "Error restoring backup", e)
return false
} finally {
tempDir.deleteRecursively()
}
}
}
42 changes: 24 additions & 18 deletions app/src/main/java/com/d4vram/cbdcounter/CBDWidgetProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -205,35 +205,33 @@ class CBDWidgetProvider : AppWidgetProvider() {
appWidgetManager.updateAppWidget(appWidgetId, views)
}

private fun incrementCounter(context: Context) {
private fun incrementActiveCounter(context: Context) {
val today = getCurrentDateKey()
val sharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
val currentCount = sharedPrefs.getInt("$KEY_COUNT_PREFIX$today", 0)

sharedPrefs.edit()
.putInt("$KEY_COUNT_PREFIX$today", currentCount + 1)
.apply()
Prefs.incrementActiveCount(context, today)
}

private fun addStandardCBD(context: Context) {
incrementCounter(context)
val entry = "πŸ”Ή ${getCurrentTimestamp()}"
incrementActiveCounter(context)
val isThc = Prefs.getSubstanceType(context) == "THC"
val entry = if (isThc) "🟒 ${getCurrentTimestamp()}" else "πŸ”Ή ${getCurrentTimestamp()}"
appendNote(context, entry)
}

private fun resetCBD(context: Context) {
val today = getCurrentDateKey()
val sharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

sharedPrefs.edit()
.putInt("$KEY_COUNT_PREFIX$today", 0)
.apply()
// Reset solo el contador del modo activo
val isThc = Prefs.getSubstanceType(context) == "THC"
if (isThc) {
Prefs.setThcCount(context, today, 0)
} else {
Prefs.setCbdCount(context, today, 0)
}
}

private fun getCurrentCount(context: Context): Int {
val today = getCurrentDateKey()
val sharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
return sharedPrefs.getInt("$KEY_COUNT_PREFIX$today", 0)
// Devolver el contador del modo activo
return Prefs.getActiveCount(context, today)
}

private fun getCurrentDateKey(): String {
Expand All @@ -247,17 +245,25 @@ class CBDWidgetProvider : AppWidgetProvider() {
}

private fun addWeed(context: Context) {
incrementCounter(context)
// Weed SIEMPRE suma a THC
incrementThcCounter(context)
val entry = "🌿 ${getCurrentTimestamp()} (aliñado con weed)"
appendNote(context, entry)
}

private fun addPolem(context: Context) {
incrementCounter(context)
// Polen SIEMPRE suma a THC
incrementThcCounter(context)
val entry = "🍫 ${getCurrentTimestamp()} (aliñado con polen)"
appendNote(context, entry)
}

private fun incrementThcCounter(context: Context) {
val today = getCurrentDateKey()
val currentThc = Prefs.getThcCount(context, today)
Prefs.setThcCount(context, today, currentThc + 1)
}

private fun appendNote(context: Context, entry: String) {
val today = getCurrentDateKey()
val currentNote = Prefs.getNote(context, today)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.util.Calendar
import java.util.Date
import java.util.Locale

class StatsActivity : AppCompatActivity(), NoteBottomSheet.Listener {
class CalendarActivity : AppCompatActivity(), NoteBottomSheet.Listener {

private lateinit var toolbar: MaterialToolbar
private lateinit var monthLabel: TextView
Expand Down Expand Up @@ -51,6 +51,8 @@ class StatsActivity : AppCompatActivity(), NoteBottomSheet.Listener {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_stats)

window.statusBarColor = getColor(R.color.gradient_start)

toolbar = findViewById(R.id.statsToolbar)
monthLabel = findViewById(R.id.monthLabel)
prevMonthButton = findViewById(R.id.prevMonthButton)
Expand Down Expand Up @@ -159,9 +161,8 @@ class StatsActivity : AppCompatActivity(), NoteBottomSheet.Listener {
for (day in 1..daysInMonth) {
workingCalendar.set(Calendar.DAY_OF_MONTH, day)
val dateKey = dateKeyFormat.format(workingCalendar.time)
val prefKey = "count_$dateKey"
val hasData = sharedPrefs.contains(prefKey)
val count = sharedPrefs.getInt(prefKey, 0)
val count = Prefs.getTotalCount(this, dateKey)
val hasData = count > 0
val emoji = if (hasData) EmojiUtils.emojiForCount(count, this) else ""
val isToday = dateKey == todayKey &&
todayCalendar.get(Calendar.MONTH) == currentMonth &&
Expand Down
Loading