~cytrogen/vbhelper

08e3b844a48b8326f01f6695110ea6cb70dda101 — Nacho 1 year, 3 months ago fbbb8f6
A few things here
- Adding new mons is working
- database is also working (añlthough we are using a temporary domain model)
- Insertion should be working too
- I used an appcontainer for the dependency injection, maybe this is not the best approach, but I don't really know any other approaches

Known bug:
- When inserting a new mon, you need to reload the app in order for the storage view to refresh correctly, I don't know what happens and why, probably because I did not create a proper ViewModel to accompany the storage part... currently this is very barebones, but it works!
M app/build.gradle.kts => app/build.gradle.kts +1 -0
@@ 46,6 46,7 @@ dependencies {
    ksp(libs.androidx.room.compiler)
    annotationProcessor(libs.androidx.room.compiler)
    implementation(libs.androidx.core.ktx)
    implementation("androidx.room:room-ktx:2.6.1")
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))

M app/src/main/AndroidManifest.xml => app/src/main/AndroidManifest.xml +1 -0
@@ 6,6 6,7 @@
    <uses-feature android:name="android.hardware.nfc" android:required="true" />

    <application
        android:name=".di.VBHelper"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"

M app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt => app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt +98 -5
@@ 15,11 15,18 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.github.nacabaro.vbhelper.navigation.AppNavigation
import com.github.cfogrady.vbnfc.CryptographicTransformer
import com.github.cfogrady.vbnfc.R
import com.github.cfogrady.vbnfc.TagCommunicator
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.DeviceType
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryBECharacterData
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryTransformationHistory
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
import kotlinx.coroutines.flow.MutableStateFlow



@@ 41,7 48,6 @@ class MainActivity : ComponentActivity() {
        }
        nfcAdapter = maybeNfcAdapter


        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {


@@ 54,12 60,15 @@ class MainActivity : ComponentActivity() {
    @Composable
    private fun MainApplication() {
        var isDoneReadingCharacter by remember { mutableStateOf(false) }
        val application = LocalContext.current.applicationContext as VBHelper
        val storageRepository = application.container.db
        AppNavigation(
            isDoneReadingCharacter = isDoneReadingCharacter,
            onClickRead = {
                handleTag {
                    val character = it.receiveCharacter()
                    nfcCharacter.value = character
                    addCharacterScannedIntoDatabase()
                    isDoneReadingCharacter = true
                    "Done reading character"
                }


@@ 74,10 83,10 @@ class MainActivity : ComponentActivity() {
    private fun getMapOfCryptographicTransformers(): Map<UShort, CryptographicTransformer> {
        return mapOf(
            Pair(DeviceType.VitalBraceletBEDeviceType,
                CryptographicTransformer(readableHmacKey1 = resources.getString(com.github.cfogrady.vbnfc.R.string.password1),
                    readableHmacKey2 = resources.getString(com.github.cfogrady.vbnfc.R.string.password2),
                    aesKey = resources.getString(com.github.cfogrady.vbnfc.R.string.decryptionKey),
                    substitutionCipher = resources.getIntArray(com.github.cfogrady.vbnfc.R.array.substitutionArray))),
                CryptographicTransformer(readableHmacKey1 = resources.getString(R.string.password1),
                    readableHmacKey2 = resources.getString(R.string.password2),
                    aesKey = resources.getString(R.string.decryptionKey),
                    substitutionCipher = resources.getIntArray(R.array.substitutionArray))),
//            Pair(DeviceType.VitalSeriesDeviceType,
//                CryptographicTransformer(hmacKey1 = resources.getString(R.string.password1),
//                    hmacKey2 = resources.getString(R.string.password2),


@@ 138,4 147,88 @@ class MainActivity : ComponentActivity() {
            nfcAdapter.disableReaderMode(this)
        }
    }

    //
    /*
    TODO:
    - Make it able to detect the different model of watches
    - Support for regular VB

    The good news is that the theory behind inserting to the database should be working
    now, it's a matter of implementing the functionality to parse dim/bem cards and use my
    domain model.
     */
    private fun addCharacterScannedIntoDatabase() {
        val beCharacter = nfcCharacter as MutableStateFlow<BENfcCharacter?>
        val temporaryCharacterData = TemporaryCharacterData(
            dimId = nfcCharacter.value!!.dimId.toInt(),
            charIndex = nfcCharacter.value!!.charIndex.toInt(),
            stage = nfcCharacter.value!!.stage.toInt(),
            attribute = nfcCharacter.value!!.attribute,
            ageInDays = nfcCharacter.value!!.ageInDays.toInt(),
            nextAdventureMissionStage = nfcCharacter.value!!.nextAdventureMissionStage.toInt(),
            mood = nfcCharacter.value!!.mood.toInt(),
            vitalPoints = nfcCharacter.value!!.vitalPoints.toInt(),
            transformationCountdown = nfcCharacter.value!!.transformationCountdown.toInt(),
            injuryStatus = nfcCharacter.value!!.injuryStatus,
            trophies = nfcCharacter.value!!.trophies.toInt(),
            currentPhaseBattlesWon = nfcCharacter.value!!.currentPhaseBattlesWon.toInt(),
            currentPhaseBattlesLost = nfcCharacter.value!!.currentPhaseBattlesLost.toInt(),
            totalBattlesWon = nfcCharacter.value!!.totalBattlesWon.toInt(),
            totalBattlesLost = nfcCharacter.value!!.totalBattlesLost.toInt(),
            activityLevel = nfcCharacter.value!!.activityLevel.toInt(),
            heartRateCurrent = nfcCharacter.value!!.heartRateCurrent.toInt()
        )

        val application = applicationContext as VBHelper
        val storageRepository = application.container.db
        val characterId = storageRepository
            .temporaryMonsterDao()
            .insertCharacterData(temporaryCharacterData)

        val temporaryBECharacterData = TemporaryBECharacterData(
            id = characterId,
            trainingHp = beCharacter.value!!.trainingHp.toInt(),
            trainingAp = beCharacter.value!!.trainingAp.toInt(),
            trainingBp = beCharacter.value!!.trainingBp.toInt(),
            remainingTrainingTimeInMinutes = beCharacter.value!!.remainingTrainingTimeInMinutes.toInt(),
            itemEffectActivityLevelValue = beCharacter.value!!.itemEffectActivityLevelValue.toInt(),
            itemEffectMentalStateValue = beCharacter.value!!.itemEffectMentalStateValue.toInt(),
            itemEffectMentalStateMinutesRemaining = beCharacter.value!!.itemEffectMentalStateMinutesRemaining.toInt(),
            itemEffectActivityLevelMinutesRemaining = beCharacter.value!!.itemEffectActivityLevelMinutesRemaining.toInt(),
            itemEffectVitalPointsChangeValue = beCharacter.value!!.itemEffectVitalPointsChangeValue.toInt(),
            itemEffectVitalPointsChangeMinutesRemaining = beCharacter.value!!.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
            abilityRarity = beCharacter.value!!.abilityRarity,
            abilityType = beCharacter.value!!.abilityType.toInt(),
            abilityBranch = beCharacter.value!!.abilityBranch.toInt(),
            abilityReset = beCharacter.value!!.abilityReset.toInt(),
            rank = beCharacter.value!!.abilityReset.toInt(),
            itemType = beCharacter.value!!.itemType.toInt(),
            itemMultiplier = beCharacter.value!!.itemMultiplier.toInt(),
            itemRemainingTime = beCharacter.value!!.itemRemainingTime.toInt(),
            otp0 = "", //beCharacter.value!!.otp0.toString(),
            otp1 = "", //beCharacter.value!!.otp1.toString(),
            minorVersion = beCharacter.value!!.characterCreationFirmwareVersion.minorVersion.toInt(),
            majorVersion = beCharacter.value!!.characterCreationFirmwareVersion.majorVersion.toInt(),
        )

        storageRepository
            .temporaryMonsterDao()
            .insertBECharacterData(temporaryBECharacterData)

        val transformationHistoryWatch = beCharacter.value!!.transformationHistory
        val domainTransformationHistory = transformationHistoryWatch.map { item ->
            TemporaryTransformationHistory(
                monId = characterId,
                toCharIndex = item.toCharIndex.toInt(),
                yearsSince1988 = item.yearsSince1988.toInt(),
                month = item.month.toInt(),
                day = item.day.toInt()
            )
        }

        storageRepository
            .temporaryMonsterDao()
            .insertTransformationHistory(*domainTransformationHistory.toTypedArray())
    }
}

M app/src/main/java/com/github/nacabaro/vbhelper/database/AppDatabase.kt => app/src/main/java/com/github/nacabaro/vbhelper/database/AppDatabase.kt +2 -9
@@ 2,15 2,7 @@ package com.github.nacabaro.vbhelper.database

import androidx.room.Database
import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.domain.Dim
import com.github.nacabaro.vbhelper.domain.DimProgress
import com.github.nacabaro.vbhelper.domain.Evolutions
import com.github.nacabaro.vbhelper.domain.Mon
import com.github.nacabaro.vbhelper.domain.User
import com.github.nacabaro.vbhelper.domain.UserHealthData
import com.github.nacabaro.vbhelper.domain.UserMonsters
import com.github.nacabaro.vbhelper.domain.UserMonstersSpecialMissions
import com.github.nacabaro.vbhelper.domain.UserStepsData
import com.github.nacabaro.vbhelper.temporary_daos.TemporaryMonsterDao
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryBECharacterData
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryTransformationHistory


@@ 24,5 16,6 @@ import com.github.nacabaro.vbhelper.temporary_domain.TemporaryTransformationHist
    ]
)
abstract class AppDatabase : RoomDatabase() {
    abstract fun temporaryMonsterDao(): TemporaryMonsterDao

}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/di/VBHelper.kt => app/src/main/java/com/github/nacabaro/vbhelper/di/VBHelper.kt +13 -0
@@ 0,0 1,13 @@
package com.github.nacabaro.vbhelper.di

import DefaultAppContainer
import android.app.Application

class VBHelper : Application() {
    lateinit var container: DefaultAppContainer

    override fun onCreate() {
        super.onCreate()
        container = DefaultAppContainer(applicationContext)
    }
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/screens/StorageScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/StorageScreen.kt +44 -3
@@ 1,5 1,6 @@
package com.github.nacabaro.vbhelper.screens

import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable


@@ 9,35 10,75 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch


@Composable
fun StorageScreen() {
    val coroutineScope = rememberCoroutineScope()
    val application = LocalContext.current.applicationContext as VBHelper
    val storageRepository = StorageRepository(application.container.db)
    val monList = remember { mutableStateListOf<TemporaryCharacterData>() }


    LaunchedEffect(storageRepository) {
        coroutineScope.launch {
            monList.clear()
            monList.addAll(storageRepository.getAllCharacters())
            Log.d("StorageScreen", "Updated data: $monList")
        }
    }

    Log.d("StorageScreen", "monList: $monList")

    Scaffold (
        topBar = { TopBanner(text = "My Digimon") }
    ) { contentPadding ->
        if (monList.isEmpty()) {
            Text(
                text = "Nothing to see here",
                modifier = Modifier
                    .padding(8.dp)
            )
        }

        LazyVerticalGrid(
            columns = GridCells.Fixed(3),
            modifier = Modifier
                .scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
                .padding(top = contentPadding.calculateTopPadding())
        ) {
            items(100) { i ->
            items(monList) { index ->
                StorageEntry(
                    name = "Digimon $i",
                    icon = R.drawable.baseline_question_mark_24
                    name = index.dimId.toString() + " - " + index.charIndex.toString(),
                    icon = R.drawable.ic_launcher_foreground,
                    modifier = Modifier
                        .padding(8.dp)
                )
            }
        }

A app/src/main/java/com/github/nacabaro/vbhelper/source/StorageRepository.kt => app/src/main/java/com/github/nacabaro/vbhelper/source/StorageRepository.kt +12 -0
@@ 0,0 1,12 @@
package com.github.nacabaro.vbhelper.source

import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData

class StorageRepository (
    private val db: AppDatabase
) {
    suspend fun getAllCharacters(): List<TemporaryCharacterData> {
        return db.temporaryMonsterDao().getAllCharacters()
    }
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/temporary_daos/TemporaryMonsterDao.kt => app/src/main/java/com/github/nacabaro/vbhelper/temporary_daos/TemporaryMonsterDao.kt +27 -0
@@ 0,0 1,27 @@
package com.github.nacabaro.vbhelper.temporary_daos

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryBECharacterData
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryTransformationHistory

@Dao
interface TemporaryMonsterDao {
    @Insert
    fun insertCharacterData(temporaryCharacterData: TemporaryCharacterData): Int

    @Insert
    fun insertBECharacterData(temporaryBECharacterData: TemporaryBECharacterData)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertTransformationHistory(vararg transformationHistory: TemporaryTransformationHistory)

    @Query("SELECT * FROM TemporaryTransformationHistory WHERE monId = :monId")
    fun getTransformationHistory(monId: Int): List<TemporaryTransformationHistory>

    @Query("SELECT * FROM TemporaryCharacterData")
    suspend fun getAllCharacters(): List<TemporaryCharacterData>
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/temporary_domain/TemporaryBECharacterData.kt => app/src/main/java/com/github/nacabaro/vbhelper/temporary_domain/TemporaryBECharacterData.kt +21 -19
@@ 3,6 3,7 @@ package com.github.nacabaro.vbhelper.temporary_domain
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.be.FirmwareVersion
import com.github.cfogrady.vbnfc.data.NfcCharacter



@@ 11,32 12,33 @@ import com.github.cfogrady.vbnfc.data.NfcCharacter
        ForeignKey(
            entity = TemporaryCharacterData::class,
            parentColumns = ["id"],
            childColumns = ["userId"],
            childColumns = ["id"],
            onDelete = ForeignKey.CASCADE
        )
    ]
)
data class TemporaryBECharacterData (
    @PrimaryKey(autoGenerate = true) val id: Int,
    val trainingHp: UShort,
    val trainingAp: UShort,
    val trainingBp: UShort,
    val remainingTrainingTimeInMinutes: UShort,
    val itemEffectMentalStateValue: Byte,
    val itemEffectMentalStateMinutesRemaining: Byte,
    val itemEffectActivityLevelValue: Byte,
    val itemEffectActivityLevelMinutesRemaining: Byte,
    val itemEffectVitalPointsChangeValue: Byte,
    val itemEffectVitalPointsChangeMinutesRemaining: Byte,
    val trainingHp: Int,
    val trainingAp: Int,
    val trainingBp: Int,
    val remainingTrainingTimeInMinutes: Int,
    val itemEffectMentalStateValue: Int,
    val itemEffectMentalStateMinutesRemaining: Int,
    val itemEffectActivityLevelValue: Int,
    val itemEffectActivityLevelMinutesRemaining: Int,
    val itemEffectVitalPointsChangeValue: Int,
    val itemEffectVitalPointsChangeMinutesRemaining: Int,
    val abilityRarity: NfcCharacter.AbilityRarity,
    val abilityType: UShort,
    val abilityBranch: UShort,
    val abilityReset: Byte,
    val rank: Byte,
    val itemType: Byte,
    val itemMultiplier: Byte,
    val itemRemainingTime: Byte,
    val abilityType: Int,
    val abilityBranch: Int,
    val abilityReset: Int,
    val rank: Int,
    val itemType: Int,
    val itemMultiplier: Int,
    val itemRemainingTime: Int,
    val otp0: String,
    val otp1: String,
    var characterCreationFirmwareVersion: FirmwareVersion,
    val minorVersion: Int,
    val majorVersion: Int,
)
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/temporary_domain/TemporaryCharacterData.kt => app/src/main/java/com/github/nacabaro/vbhelper/temporary_domain/TemporaryCharacterData.kt +16 -38
@@ 4,46 4,24 @@ import androidx.room.Entity
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter

/*
dimId=16,
charIndex=8,
stage=4,
attribute=Free,
ageInDays=0,
nextAdventureMissionStage=9,
mood=99,
vitalPoints=9999,
transformationCountdown=1101,
injuryStatus=None,
trophies=0,
currentPhaseBattlesWon=19,
currentPhaseBattlesLost=4,
totalBattlesWon=36,
totalBattlesLost=10,
activityLevel=0,
heartRateCurrent=71,
*/


@Entity
data class TemporaryCharacterData (
    @PrimaryKey(autoGenerate = true) val id: Int,
    val dimId: UShort,
    var charIndex: UShort,
    var stage: Byte,
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val dimId: Int,
    var charIndex: Int,
    var stage: Int,
    var attribute: NfcCharacter.Attribute,
    var ageInDays: Byte,
    var nextAdventureMissionStage: Byte, // next adventure mission stage on the character's dim
    var mood: Byte,
    var vitalPoints: UShort,
    var transformationCountdown: UShort,
    var ageInDays: Int,
    var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
    var mood: Int,
    var vitalPoints: Int,
    var transformationCountdown: Int,
    var injuryStatus: NfcCharacter.InjuryStatus,
    var trophies: UShort,
    var currentPhaseBattlesWon: UShort,
    var currentPhaseBattlesLost: UShort,
    var totalBattlesWon: UShort,
    var totalBattlesLost: UShort,
    var activityLevel: Byte,
    var heartRateCurrent: UByte,
    var transformationHistory: Int
    var trophies: Int,
    var currentPhaseBattlesWon: Int,
    var currentPhaseBattlesLost: Int,
    var totalBattlesWon: Int,
    var totalBattlesLost: Int,
    var activityLevel: Int,
    var heartRateCurrent: Int,
)
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/temporary_domain/TemporaryTransformationHistory.kt => app/src/main/java/com/github/nacabaro/vbhelper/temporary_domain/TemporaryTransformationHistory.kt +17 -5
@@ 1,13 1,25 @@
package com.github.nacabaro.vbhelper.temporary_domain

import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey

@Entity
@Entity(
    foreignKeys = [
        ForeignKey(
            entity = TemporaryCharacterData::class,
            parentColumns = ["id"],
            childColumns = ["monId"],
            onDelete = ForeignKey.CASCADE
        )
    ]
)
// Bit lazy, will correct later...
data class TemporaryTransformationHistory (
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val monId: Int,
    val toCharIndex: Byte,
    val yearsSince1988: Byte,
    val month: Byte,
    val day: Byte
    val toCharIndex: Int,
    val yearsSince1988: Int,
    val month: Int,
    val day: Int
)

A app/src/main/java/com/github/nacabaro/vbhelper/vm/StorageViewModel.kt => app/src/main/java/com/github/nacabaro/vbhelper/vm/StorageViewModel.kt +2 -0
@@ 0,0 1,2 @@
package com.github.nacabaro.vbhelper.vm