Compare commits

..
Author SHA1 Message Date
Nacho 08e3b844a4 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!
2025-01-04 18:49:16 +01:00
Nacho fbbb8f6ad1 Merge branch 'refs/heads/nfc/reading_character' into database/initialise 2025-01-04 17:35:50 +01:00
Nacho 859786c7f3 Updating database with temporary daos in order to have an MVP
Will be changing this to the pre-established model once I sort out DiM/BEM import
2025-01-04 16:51:11 +01:00
Nacho 2f4bc9dba6 Added temporary domain objects 2025-01-04 16:42:50 +01:00
Nacho e9eedb7429 Started working in database initialization NOT WORKING YET 2025-01-04 16:10:23 +01:00
19 changed files with 393 additions and 8 deletions
+1
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))
+1
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"
@@ -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())
}
} }
@@ -0,0 +1,16 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.Dim
import kotlinx.coroutines.flow.Flow
@Dao
interface DiMDao {
@Insert
suspend fun insertNewDim(dim: Dim)
@Query("SELECT * FROM Dim")
fun getAllDims(): Flow<List<Dim>>
}
@@ -0,0 +1,11 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.DimProgress
@Dao
interface DiMProgressDao {
@Upsert
suspend fun updateDimProgress(vararg dimProgress: DimProgress)
}
@@ -0,0 +1,15 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.UserMonsters
@Dao
interface UserMonstersDao {
@Insert
fun insertUserMonsters(userMonsters: UserMonsters)
@Query("SELECT * FROM UserMonsters WHERE userId = :userId")
fun getUserMonsters(userId: Int): List<UserMonsters>
}
@@ -0,0 +1,11 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Insert
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryTransformationHistory
@Dao
interface UserMonstersTransformationHistoryDao {
@Insert
fun insertTransformations(vararg transformations: TemporaryTransformationHistory)
}
@@ -0,0 +1,21 @@
package com.github.nacabaro.vbhelper.database
import androidx.room.Database
import androidx.room.RoomDatabase
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
@Database(
version = 1,
entities = [
TemporaryCharacterData::class,
TemporaryBECharacterData::class,
TemporaryTransformationHistory::class
]
)
abstract class AppDatabase : RoomDatabase() {
abstract fun temporaryMonsterDao(): TemporaryMonsterDao
}
@@ -0,0 +1,7 @@
package com.github.nacabaro.vbhelper.di
import com.github.nacabaro.vbhelper.database.AppDatabase
interface AppContainer {
val db: AppDatabase
}
@@ -0,0 +1,14 @@
import android.content.Context
import androidx.room.Room
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.AppContainer
class DefaultAppContainer(private val context: Context) : AppContainer {
override val db: AppDatabase by lazy {
Room.databaseBuilder(
context = context,
klass = AppDatabase::class.java,
"internalDb"
).build()
}
}
@@ -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)
}
}
@@ -0,0 +1,4 @@
package com.github.nacabaro.vbhelper.dtos
class MonsterDataCombined {
}
@@ -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)
) )
} }
} }
@@ -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()
}
}
@@ -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>
}
@@ -0,0 +1,44 @@
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
@Entity(
foreignKeys = [
ForeignKey(
entity = TemporaryCharacterData::class,
parentColumns = ["id"],
childColumns = ["id"],
onDelete = ForeignKey.CASCADE
)
]
)
data class TemporaryBECharacterData (
@PrimaryKey(autoGenerate = true) val id: Int,
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: 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,
val minorVersion: Int,
val majorVersion: Int,
)
@@ -0,0 +1,27 @@
package com.github.nacabaro.vbhelper.temporary_domain
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter
@Entity
data class TemporaryCharacterData (
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val dimId: Int,
var charIndex: Int,
var stage: Int,
var attribute: NfcCharacter.Attribute,
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: Int,
var currentPhaseBattlesWon: Int,
var currentPhaseBattlesLost: Int,
var totalBattlesWon: Int,
var totalBattlesLost: Int,
var activityLevel: Int,
var heartRateCurrent: Int,
)
@@ -0,0 +1,25 @@
package com.github.nacabaro.vbhelper.temporary_domain
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@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: Int,
val yearsSince1988: Int,
val month: Int,
val day: Int
)
@@ -0,0 +1,2 @@
package com.github.nacabaro.vbhelper.vm