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!
This commit is contained in:
Nacho 2025-01-04 18:49:16 +01:00
parent fbbb8f6ad1
commit 08e3b844a4
12 changed files with 254 additions and 79 deletions

View File

@ -46,6 +46,7 @@ dependencies {
ksp(libs.androidx.room.compiler) ksp(libs.androidx.room.compiler)
annotationProcessor(libs.androidx.room.compiler) annotationProcessor(libs.androidx.room.compiler)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation("androidx.room:room-ktx:2.6.1")
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom)) implementation(platform(libs.androidx.compose.bom))

View File

@ -6,6 +6,7 @@
<uses-feature android:name="android.hardware.nfc" android:required="true" /> <uses-feature android:name="android.hardware.nfc" android:required="true" />
<application <application
android:name=".di.VBHelper"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"

View File

@ -15,11 +15,18 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.github.nacabaro.vbhelper.navigation.AppNavigation import com.github.nacabaro.vbhelper.navigation.AppNavigation
import com.github.cfogrady.vbnfc.CryptographicTransformer import com.github.cfogrady.vbnfc.CryptographicTransformer
import com.github.cfogrady.vbnfc.R
import com.github.cfogrady.vbnfc.TagCommunicator 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.DeviceType
import com.github.cfogrady.vbnfc.data.NfcCharacter 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 com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -41,7 +48,6 @@ class MainActivity : ComponentActivity() {
} }
nfcAdapter = maybeNfcAdapter nfcAdapter = maybeNfcAdapter
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
@ -54,12 +60,15 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
private fun MainApplication() { private fun MainApplication() {
var isDoneReadingCharacter by remember { mutableStateOf(false) } var isDoneReadingCharacter by remember { mutableStateOf(false) }
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = application.container.db
AppNavigation( AppNavigation(
isDoneReadingCharacter = isDoneReadingCharacter, isDoneReadingCharacter = isDoneReadingCharacter,
onClickRead = { onClickRead = {
handleTag { handleTag {
val character = it.receiveCharacter() val character = it.receiveCharacter()
nfcCharacter.value = character nfcCharacter.value = character
addCharacterScannedIntoDatabase()
isDoneReadingCharacter = true isDoneReadingCharacter = true
"Done reading character" "Done reading character"
} }
@ -74,10 +83,10 @@ class MainActivity : ComponentActivity() {
private fun getMapOfCryptographicTransformers(): Map<UShort, CryptographicTransformer> { private fun getMapOfCryptographicTransformers(): Map<UShort, CryptographicTransformer> {
return mapOf( return mapOf(
Pair(DeviceType.VitalBraceletBEDeviceType, Pair(DeviceType.VitalBraceletBEDeviceType,
CryptographicTransformer(readableHmacKey1 = resources.getString(com.github.cfogrady.vbnfc.R.string.password1), CryptographicTransformer(readableHmacKey1 = resources.getString(R.string.password1),
readableHmacKey2 = resources.getString(com.github.cfogrady.vbnfc.R.string.password2), readableHmacKey2 = resources.getString(R.string.password2),
aesKey = resources.getString(com.github.cfogrady.vbnfc.R.string.decryptionKey), aesKey = resources.getString(R.string.decryptionKey),
substitutionCipher = resources.getIntArray(com.github.cfogrady.vbnfc.R.array.substitutionArray))), substitutionCipher = resources.getIntArray(R.array.substitutionArray))),
// Pair(DeviceType.VitalSeriesDeviceType, // Pair(DeviceType.VitalSeriesDeviceType,
// CryptographicTransformer(hmacKey1 = resources.getString(R.string.password1), // CryptographicTransformer(hmacKey1 = resources.getString(R.string.password1),
// hmacKey2 = resources.getString(R.string.password2), // hmacKey2 = resources.getString(R.string.password2),
@ -138,4 +147,88 @@ class MainActivity : ComponentActivity() {
nfcAdapter.disableReaderMode(this) 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())
}
} }

View File

@ -2,15 +2,7 @@ package com.github.nacabaro.vbhelper.database
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.domain.Dim import com.github.nacabaro.vbhelper.temporary_daos.TemporaryMonsterDao
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_domain.TemporaryBECharacterData import com.github.nacabaro.vbhelper.temporary_domain.TemporaryBECharacterData
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryTransformationHistory 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 class AppDatabase : RoomDatabase() {
abstract fun temporaryMonsterDao(): TemporaryMonsterDao
} }

View File

@ -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)
}
}

View File

@ -1,5 +1,6 @@
package com.github.nacabaro.vbhelper.screens package com.github.nacabaro.vbhelper.screens
import android.util.Log
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable 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.layout.size
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.R import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.TopBanner 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 @Composable
fun StorageScreen() { 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 ( Scaffold (
topBar = { TopBanner(text = "My Digimon") } topBar = { TopBanner(text = "My Digimon") }
) { contentPadding -> ) { contentPadding ->
if (monList.isEmpty()) {
Text(
text = "Nothing to see here",
modifier = Modifier
.padding(8.dp)
)
}
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
modifier = Modifier modifier = Modifier
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical) .scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
items(100) { i -> items(monList) { index ->
StorageEntry( StorageEntry(
name = "Digimon $i", name = index.dimId.toString() + " - " + index.charIndex.toString(),
icon = R.drawable.baseline_question_mark_24 icon = R.drawable.ic_launcher_foreground,
modifier = Modifier
.padding(8.dp)
) )
} }
} }

View File

@ -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()
}
}

View File

@ -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>
}

View File

@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.temporary_domain
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.be.FirmwareVersion import com.github.cfogrady.vbnfc.be.FirmwareVersion
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
@ -11,32 +12,33 @@ import com.github.cfogrady.vbnfc.data.NfcCharacter
ForeignKey( ForeignKey(
entity = TemporaryCharacterData::class, entity = TemporaryCharacterData::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["userId"], childColumns = ["id"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
] ]
) )
data class TemporaryBECharacterData ( data class TemporaryBECharacterData (
@PrimaryKey(autoGenerate = true) val id: Int, @PrimaryKey(autoGenerate = true) val id: Int,
val trainingHp: UShort, val trainingHp: Int,
val trainingAp: UShort, val trainingAp: Int,
val trainingBp: UShort, val trainingBp: Int,
val remainingTrainingTimeInMinutes: UShort, val remainingTrainingTimeInMinutes: Int,
val itemEffectMentalStateValue: Byte, val itemEffectMentalStateValue: Int,
val itemEffectMentalStateMinutesRemaining: Byte, val itemEffectMentalStateMinutesRemaining: Int,
val itemEffectActivityLevelValue: Byte, val itemEffectActivityLevelValue: Int,
val itemEffectActivityLevelMinutesRemaining: Byte, val itemEffectActivityLevelMinutesRemaining: Int,
val itemEffectVitalPointsChangeValue: Byte, val itemEffectVitalPointsChangeValue: Int,
val itemEffectVitalPointsChangeMinutesRemaining: Byte, val itemEffectVitalPointsChangeMinutesRemaining: Int,
val abilityRarity: NfcCharacter.AbilityRarity, val abilityRarity: NfcCharacter.AbilityRarity,
val abilityType: UShort, val abilityType: Int,
val abilityBranch: UShort, val abilityBranch: Int,
val abilityReset: Byte, val abilityReset: Int,
val rank: Byte, val rank: Int,
val itemType: Byte, val itemType: Int,
val itemMultiplier: Byte, val itemMultiplier: Int,
val itemRemainingTime: Byte, val itemRemainingTime: Int,
val otp0: String, val otp0: String,
val otp1: String, val otp1: String,
var characterCreationFirmwareVersion: FirmwareVersion, val minorVersion: Int,
val majorVersion: Int,
) )

View File

@ -4,46 +4,24 @@ import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter 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 @Entity
data class TemporaryCharacterData ( data class TemporaryCharacterData (
@PrimaryKey(autoGenerate = true) val id: Int, @PrimaryKey(autoGenerate = true) val id: Int = 0,
val dimId: UShort, val dimId: Int,
var charIndex: UShort, var charIndex: Int,
var stage: Byte, var stage: Int,
var attribute: NfcCharacter.Attribute, var attribute: NfcCharacter.Attribute,
var ageInDays: Byte, var ageInDays: Int,
var nextAdventureMissionStage: Byte, // next adventure mission stage on the character's dim var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
var mood: Byte, var mood: Int,
var vitalPoints: UShort, var vitalPoints: Int,
var transformationCountdown: UShort, var transformationCountdown: Int,
var injuryStatus: NfcCharacter.InjuryStatus, var injuryStatus: NfcCharacter.InjuryStatus,
var trophies: UShort, var trophies: Int,
var currentPhaseBattlesWon: UShort, var currentPhaseBattlesWon: Int,
var currentPhaseBattlesLost: UShort, var currentPhaseBattlesLost: Int,
var totalBattlesWon: UShort, var totalBattlesWon: Int,
var totalBattlesLost: UShort, var totalBattlesLost: Int,
var activityLevel: Byte, var activityLevel: Int,
var heartRateCurrent: UByte, var heartRateCurrent: Int,
var transformationHistory: Int
) )

View File

@ -1,13 +1,25 @@
package com.github.nacabaro.vbhelper.temporary_domain package com.github.nacabaro.vbhelper.temporary_domain
import androidx.room.Entity 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... // Bit lazy, will correct later...
data class TemporaryTransformationHistory ( data class TemporaryTransformationHistory (
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val monId: Int, val monId: Int,
val toCharIndex: Byte, val toCharIndex: Int,
val yearsSince1988: Byte, val yearsSince1988: Int,
val month: Byte, val month: Int,
val day: Byte val day: Int
) )

View File

@ -0,0 +1,2 @@
package com.github.nacabaro.vbhelper.vm