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

View File

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

View File

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

View File

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

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

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.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,
)

View File

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

View File

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

View File

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