A lot more things

- Moved scan button to inside the home screen
- Added items screen
- Scanning functionality updated to work with BEms
- Different home screen layouts depending on the configuration (BE with BEm, BE with DIm, since they don't have the same data, looking at you, special training)
- It is now possible to import BEms
- Character size is now more accurate, (no more big babies)
- Once sent to the watch, characters are deleted from the app, and it shouldn't fail
- Updated domain model to support evolution history.

TODO:
- Reorganize some of the code, mostly SQL queries and reorganize the scan screen functionality
- Create home layout for the VB watch
- Start figuring out reading data from the VB
    - Also create VB data domain model
    - Also start figuring out writing to the VB (it's 3 steps)
- Block off scan button until secrets are imported
- Start working with the dex
    - Update domain model
    - Use cfogrady's blank character when not seen
- Cancel character upload in case the app database does not contain the card for it (otherwise, the watch will delete the character)
- Export character data (in case of any app issues or changing phones)

(Also added some more icons)

Woo
This commit is contained in:
Nacho 2025-01-16 01:03:57 +01:00
parent ce1cf3eddb
commit 5a1d52aa1d
40 changed files with 1032 additions and 128 deletions

View File

@ -11,10 +11,12 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vb.dim.card.BemCard
import com.github.cfogrady.vb.dim.card.DimReader import com.github.cfogrady.vb.dim.card.DimReader
import com.github.nacabaro.vbhelper.navigation.AppNavigation import com.github.nacabaro.vbhelper.navigation.AppNavigation
import com.github.cfogrady.vbnfc.be.BENfcCharacter import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.Dim import com.github.nacabaro.vbhelper.domain.Dim
import com.github.nacabaro.vbhelper.domain.Sprites import com.github.nacabaro.vbhelper.domain.Sprites
@ -22,6 +24,7 @@ import com.github.nacabaro.vbhelper.domain.Character
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.SettingsScreenController import com.github.nacabaro.vbhelper.screens.SettingsScreenController
@ -29,6 +32,8 @@ import com.github.nacabaro.vbhelper.source.ApkSecretsImporter
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
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Date
import java.util.GregorianCalendar
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -105,13 +110,17 @@ class MainActivity : ComponentActivity() {
inputStream.use { fileReader -> inputStream.use { fileReader ->
val dimReader = DimReader() val dimReader = DimReader()
val card = dimReader.readCard(fileReader, false) val card = dimReader.readCard(fileReader, false)
Log.i("MainActivity", "Card name: ${card is BemCard}")
val dimModel = Dim( val dimModel = Dim(
dimId = card.header.dimId, dimId = card.header.dimId,
logo = card.spriteData.sprites[0].pixelData, logo = card.spriteData.sprites[0].pixelData,
name = card.spriteData.text, // TODO Make user write card name name = card.spriteData.text, // TODO Make user write card name
stageCount = card.adventureLevels.levels.size, stageCount = card.adventureLevels.levels.size,
logoHeight = card.spriteData.sprites[0].height, logoHeight = card.spriteData.sprites[0].height,
logoWidth = card.spriteData.sprites[0].width logoWidth = card.spriteData.sprites[0].width,
isBEm = card is BemCard
) )
val dimId = storageRepository val dimId = storageRepository
@ -120,7 +129,11 @@ class MainActivity : ComponentActivity() {
val characters = card.characterStats.characterEntries val characters = card.characterStats.characterEntries
var spriteCounter = 10 var spriteCounter = when (card is BemCard) {
true -> 55
false -> 10
}
val domainCharacters = mutableListOf<Character>() val domainCharacters = mutableListOf<Character>()
for (index in 0 until characters.size) { for (index in 0 until characters.size) {
@ -144,12 +157,14 @@ class MainActivity : ComponentActivity() {
) )
// TODO: Improve this // TODO: Improve this
if (index == 0) { if (card is BemCard) {
spriteCounter += 6
} else if (index == 1) {
spriteCounter += 7
} else {
spriteCounter += 14 spriteCounter += 14
} else {
when (index) {
0 -> spriteCounter += 6
1 -> spriteCounter += 7
else -> spriteCounter += 14
}
} }
} }
@ -216,9 +231,7 @@ class MainActivity : ComponentActivity() {
.dimDao() .dimDao()
.getDimById(nfcCharacter.value!!.dimId.toInt()) .getDimById(nfcCharacter.value!!.dimId.toInt())
if (dimData == null) { if (dimData == null) return "Card not found"
return "Card not found"
}
val cardCharData = storageRepository val cardCharData = storageRepository
.characterDao() .characterDao()
@ -244,60 +257,63 @@ class MainActivity : ComponentActivity() {
characterType = when (nfcCharacter.value) { characterType = when (nfcCharacter.value) {
is BENfcCharacter -> com.github.nacabaro.vbhelper.domain.DeviceType.BEDevice is BENfcCharacter -> com.github.nacabaro.vbhelper.domain.DeviceType.BEDevice
else -> com.github.nacabaro.vbhelper.domain.DeviceType.VBDevice else -> com.github.nacabaro.vbhelper.domain.DeviceType.VBDevice
} },
isActive = true
) )
storageRepository
.userCharacterDao()
.clearActiveCharacter()
val characterId: Long = storageRepository val characterId: Long = storageRepository
.userCharacterDao() .userCharacterDao()
.insertCharacterData(characterData) .insertCharacterData(characterData)
if (nfcCharacter.value is BENfcCharacter) { if (nfcCharacter.value is BENfcCharacter) {
val beCharacter = nfcCharacter as MutableStateFlow<BENfcCharacter?> val beCharacter = nfcCharacter.value as BENfcCharacter
val extraCharacterData = BECharacterData( val extraCharacterData = BECharacterData(
id = characterId, id = characterId,
trainingHp = beCharacter.value!!.trainingHp.toInt(), trainingHp = beCharacter.trainingHp.toInt(),
trainingAp = beCharacter.value!!.trainingAp.toInt(), trainingAp = beCharacter.trainingAp.toInt(),
trainingBp = beCharacter.value!!.trainingBp.toInt(), trainingBp = beCharacter.trainingBp.toInt(),
remainingTrainingTimeInMinutes = beCharacter.value!!.remainingTrainingTimeInMinutes.toInt(), remainingTrainingTimeInMinutes = beCharacter.remainingTrainingTimeInMinutes.toInt(),
itemEffectActivityLevelValue = beCharacter.value!!.itemEffectActivityLevelValue.toInt(), itemEffectActivityLevelValue = beCharacter.itemEffectActivityLevelValue.toInt(),
itemEffectMentalStateValue = beCharacter.value!!.itemEffectMentalStateValue.toInt(), itemEffectMentalStateValue = beCharacter.itemEffectMentalStateValue.toInt(),
itemEffectMentalStateMinutesRemaining = beCharacter.value!!.itemEffectMentalStateMinutesRemaining.toInt(), itemEffectMentalStateMinutesRemaining = beCharacter.itemEffectMentalStateMinutesRemaining.toInt(),
itemEffectActivityLevelMinutesRemaining = beCharacter.value!!.itemEffectActivityLevelMinutesRemaining.toInt(), itemEffectActivityLevelMinutesRemaining = beCharacter.itemEffectActivityLevelMinutesRemaining.toInt(),
itemEffectVitalPointsChangeValue = beCharacter.value!!.itemEffectVitalPointsChangeValue.toInt(), itemEffectVitalPointsChangeValue = beCharacter.itemEffectVitalPointsChangeValue.toInt(),
itemEffectVitalPointsChangeMinutesRemaining = beCharacter.value!!.itemEffectVitalPointsChangeMinutesRemaining.toInt(), itemEffectVitalPointsChangeMinutesRemaining = beCharacter.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
abilityRarity = beCharacter.value!!.abilityRarity, abilityRarity = beCharacter.abilityRarity,
abilityType = beCharacter.value!!.abilityType.toInt(), abilityType = beCharacter.abilityType.toInt(),
abilityBranch = beCharacter.value!!.abilityBranch.toInt(), abilityBranch = beCharacter.abilityBranch.toInt(),
abilityReset = beCharacter.value!!.abilityReset.toInt(), abilityReset = beCharacter.abilityReset.toInt(),
rank = beCharacter.value!!.abilityReset.toInt(), rank = beCharacter.abilityReset.toInt(),
itemType = beCharacter.value!!.itemType.toInt(), itemType = beCharacter.itemType.toInt(),
itemMultiplier = beCharacter.value!!.itemMultiplier.toInt(), itemMultiplier = beCharacter.itemMultiplier.toInt(),
itemRemainingTime = beCharacter.value!!.itemRemainingTime.toInt(), itemRemainingTime = beCharacter.itemRemainingTime.toInt(),
otp0 = "", //beCharacter.value!!.otp0.toString(), otp0 = "", //beCharacter.value!!.otp0.toString(),
otp1 = "", //beCharacter.value!!.otp1.toString(), otp1 = "", //beCharacter.value!!.otp1.toString(),
minorVersion = beCharacter.value!!.characterCreationFirmwareVersion.minorVersion.toInt(), minorVersion = beCharacter.characterCreationFirmwareVersion.minorVersion.toInt(),
majorVersion = beCharacter.value!!.characterCreationFirmwareVersion.majorVersion.toInt(), majorVersion = beCharacter.characterCreationFirmwareVersion.majorVersion.toInt(),
) )
storageRepository storageRepository
.userCharacterDao() .userCharacterDao()
.insertBECharacterData(extraCharacterData) .insertBECharacterData(extraCharacterData)
val transformationHistoryWatch = beCharacter.value!!.transformationHistory val transformationHistoryWatch = beCharacter.transformationHistory
val domainTransformationHistory = transformationHistoryWatch.map { item -> transformationHistoryWatch.map { item ->
TransformationHistory( if (item.toCharIndex.toInt() != 255) {
monId = characterId, val date = GregorianCalendar(item.year.toInt(), item.month.toInt(), item.day.toInt())
toCharIndex = item.toCharIndex.toInt(), .time
year = item.year.toInt(), .time
month = item.month.toInt(), storageRepository
day = item.day.toInt() .characterDao()
) .insertTransformation(characterId, item.toCharIndex.toInt(), dimData.id, date)
}
} }
storageRepository } else if (nfcCharacter.value is VBNfcCharacter) {
.userCharacterDao()
.insertTransformationHistory(*domainTransformationHistory.toTypedArray())
} else {
return "Not implemented yet" return "Not implemented yet"
} }

View File

@ -1,48 +1,113 @@
package com.github.nacabaro.vbhelper.components package com.github.nacabaro.vbhelper.components
import android.graphics.Bitmap import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.domain.Sprites import androidx.compose.ui.unit.sp
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap import com.github.nacabaro.vbhelper.utils.getBitmap
import java.nio.ByteBuffer import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
@Composable @Composable
fun CharacterEntry( fun CharacterEntry(
icon: BitmapData, icon: BitmapData,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 3,
onClick: () -> Unit = { } onClick: () -> Unit = { }
) { ) {
val bitmap = remember (icon.bitmap) { val bitmap = remember (icon.bitmap) {
icon.getBitmap() icon.getBitmap()
} }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() } val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (icon.width * multiplier / density).dp
Card( Card(
shape = MaterialTheme.shapes.medium, shape = shape,
onClick = onClick, onClick = onClick,
modifier = modifier modifier = modifier
.aspectRatio(1f)
.padding(8.dp) .padding(8.dp)
.size(96.dp) ) {
Box(
contentAlignment = Alignment.BottomCenter,
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) { ) {
Image( Image(
bitmap = imageBitmap, bitmap = imageBitmap,
contentDescription = "Icon", contentDescription = "Icon",
filterQuality = FilterQuality.None, filterQuality = FilterQuality.None,
modifier = Modifier modifier = Modifier
.padding(8.dp) .size(dpSize)
.fillMaxSize()
) )
} }
} }
}
@Composable
fun ItemDisplay(
icon: Int,
textValue: String,
modifier: Modifier = Modifier,
iconSize: Dp = 48.dp,
textSize: TextUnit = 24.sp,
definition: String = "",
) {
val context = LocalContext.current
Card(
modifier = modifier,
shape = androidx.compose.material.MaterialTheme.shapes.small,
onClick = {
Toast.makeText(context, definition, Toast.LENGTH_SHORT).show()
}
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
) {
Icon(
painter = painterResource(icon),
contentDescription = "Vitals",
modifier = Modifier
.padding(8.dp)
.size(iconSize)
)
Text(
text = textValue,
textAlign = TextAlign.Center,
fontSize = textSize,
fontFamily = MaterialTheme.typography.titleLarge.fontFamily,
fontWeight = FontWeight.Bold
)
}
}
}

View File

@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.components package com.github.nacabaro.vbhelper.components
import android.graphics.Bitmap
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -15,10 +14,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap import com.github.nacabaro.vbhelper.utils.getBitmap
import java.nio.ByteBuffer
@Composable @Composable
fun DexDiMEntry( fun DexDiMEntry(
@ -27,10 +26,10 @@ fun DexDiMEntry(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val bitmap = remember (logo.bitmap) { val bitmap = remember (logo.bitmap) { logo.getBitmap() }
logo.getBitmap()
}
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() } val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (logo.width * 4 / density).dp
Card ( Card (
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
@ -49,7 +48,7 @@ fun DexDiMEntry(
filterQuality = FilterQuality.None, filterQuality = FilterQuality.None,
modifier = Modifier modifier = Modifier
.padding(8.dp) .padding(8.dp)
.size(64.dp) .size(dpSize)
) )
Text( Text(
text = name, text = name,

View File

@ -23,6 +23,7 @@ fun TopBanner(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onGearClick: (() -> Unit)? = null, onGearClick: (() -> Unit)? = null,
onBackClick: (() -> Unit)? = null, onBackClick: (() -> Unit)? = null,
onScanClick: (() -> Unit)? = null
) { ) {
Box( // Use Box to overlay elements Box( // Use Box to overlay elements
modifier = modifier modifier = modifier
@ -50,7 +51,18 @@ fun TopBanner(
} }
} }
if (onBackClick != null) { if (onScanClick != null) {
IconButton(
onClick = onScanClick,
modifier = Modifier
.align(Alignment.CenterStart) // Place gear icon at the end
) {
Icon(
painter = painterResource(R.drawable.baseline_nfc_24), // Use a gear icon
contentDescription = "Scan"
)
}
} else if (onBackClick != null) {
IconButton( IconButton(
onClick = onBackClick, onClick = onBackClick,
modifier = Modifier modifier = Modifier

View File

@ -0,0 +1,76 @@
package com.github.nacabaro.vbhelper.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
@Composable
fun TransformationHistoryCard(
transformationHistory: List<CharacterDtos.TransformationHistory>,
modifier: Modifier= Modifier
) {
Card (
shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = modifier
) {
LazyRow (
modifier = Modifier
.padding(8.dp)
) {
items(transformationHistory) { transformation ->
TransformationHistoryItem(transformation)
}
}
}
}
@Composable
fun TransformationHistoryItem(
transformation: CharacterDtos.TransformationHistory
) {
val bitmapData = BitmapData(
bitmap = transformation.spriteIdle,
width = transformation.spriteWidth,
height = transformation.spriteHeight
)
val bitmap = remember (bitmapData) { bitmapData.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (bitmap.width * 3 / density).dp
Box (
contentAlignment = Alignment.BottomCenter,
modifier = Modifier
.aspectRatio(1f)
.fillMaxWidth()
.size((64*3/density).dp)
) {
Image(
bitmap = imageBitmap,
contentDescription = "Transformation",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
)
}
}

View File

@ -6,6 +6,7 @@ import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.Character import com.github.nacabaro.vbhelper.domain.Character
import com.github.nacabaro.vbhelper.domain.Sprites import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import java.util.GregorianCalendar
@Dao @Dao
interface CharacterDao { interface CharacterDao {
@ -37,4 +38,13 @@ interface CharacterDao {
WHERE uc.id = :charId WHERE uc.id = :charId
""") """)
suspend fun getCharacterInfo(charId: Long): CharacterDtos.DiMInfo suspend fun getCharacterInfo(charId: Long): CharacterDtos.DiMInfo
@Query("""
INSERT INTO TransformationHistory(monId, stageId, transformationDate)
VALUES
(:monId,
(SELECT id FROM Character WHERE monIndex = :stage AND dimId = :dimId),
:transformationDate)
""")
fun insertTransformation(monId: Long, stage: Int, dimId: Long, transformationDate: Long)
} }

View File

@ -20,17 +20,30 @@ interface UserCharacterDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertTransformationHistory(vararg transformationHistory: TransformationHistory) fun insertTransformationHistory(vararg transformationHistory: TransformationHistory)
@Query("SELECT * FROM TransformationHistory WHERE monId = :monId") @Query("""
fun getTransformationHistory(monId: Long): List<TransformationHistory> SELECT
c.id AS id,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
c.monIndex AS monIndex,
t.transformationDate AS transformationDate
FROM TransformationHistory t
JOIN Character c ON c.id = t.stageId
WHERE monId = :monId
""")
fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?
@Query(""" @Query("""
SELECT SELECT
uc.*, uc.*,
c.sprite1 AS spriteIdle, c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth, c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight c.spritesHeight AS spriteHeight,
d.isBEm as isBemCard
FROM UserCharacter uc FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id JOIN Character c ON uc.charId = c.id
JOIN Dim d ON c.dimId = d.id
""") """)
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites> suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
@ -39,4 +52,28 @@ interface UserCharacterDao {
@Query("SELECT * FROM BECharacterData WHERE id = :id") @Query("SELECT * FROM BECharacterData WHERE id = :id")
suspend fun getBeData(id: Long): BECharacterData suspend fun getBeData(id: Long): BECharacterData
@Query("""
SELECT
uc.*,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.isBEm as isBemCard
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Dim d ON c.dimId = d.id
WHERE uc.isActive = 1
LIMIT 1
""")
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites?
@Query("DELETE FROM UserCharacter WHERE id = :id")
fun deleteCharacterById(id: Long)
@Query("UPDATE UserCharacter SET isActive = 0 WHERE isActive = 1")
fun clearActiveCharacter()
@Query("UPDATE UserCharacter SET isActive = 1 WHERE id = :id")
fun setActiveCharacter(id: Long)
} }

View File

@ -13,5 +13,6 @@ data class Dim(
val logoWidth: Int, val logoWidth: Int,
val logoHeight: Int, val logoHeight: Int,
val name: String, val name: String,
val stageCount: Int val stageCount: Int,
val isBEm: Boolean
) )

View File

@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.domain.device_data
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.nacabaro.vbhelper.domain.Character
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
@ -11,14 +12,18 @@ import androidx.room.PrimaryKey
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["monId"], childColumns = ["monId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Character::class,
parentColumns = ["id"],
childColumns = ["stageId"],
onDelete = ForeignKey.CASCADE
) )
] ]
) )
data class TransformationHistory ( data class TransformationHistory (
@PrimaryKey(autoGenerate = true) val id: Long = 0, @PrimaryKey(autoGenerate = true) val id: Long = 0,
val monId: Long, val monId: Long,
val toCharIndex: Int, val stageId: Long,
val year: Int, val transformationDate: Long
val month: Int,
val day: Int
) )

View File

@ -35,5 +35,6 @@ data class UserCharacter (
var totalBattlesLost: Int, var totalBattlesLost: Int,
var activityLevel: Int, var activityLevel: Int,
var heartRateCurrent: Int, var heartRateCurrent: Int,
var characterType: DeviceType var characterType: DeviceType,
var isActive: Boolean
) )

View File

@ -26,11 +26,21 @@ object CharacterDtos {
var characterType: DeviceType, var characterType: DeviceType,
val spriteIdle: ByteArray, val spriteIdle: ByteArray,
val spriteWidth: Int, val spriteWidth: Int,
val spriteHeight: Int val spriteHeight: Int,
val isBemCard: Boolean
) )
data class DiMInfo( data class DiMInfo(
val cardId: Int, val cardId: Int,
val charId: Int val charId: Int
) )
data class TransformationHistory(
val id: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val monIndex: Int,
val transformationDate: Long
)
} }

View File

@ -11,7 +11,8 @@ import androidx.navigation.compose.rememberNavController
import com.github.nacabaro.vbhelper.screens.BattlesScreen import com.github.nacabaro.vbhelper.screens.BattlesScreen
import com.github.nacabaro.vbhelper.screens.DexScreen import com.github.nacabaro.vbhelper.screens.DexScreen
import com.github.nacabaro.vbhelper.screens.DiMScreen import com.github.nacabaro.vbhelper.screens.DiMScreen
import com.github.nacabaro.vbhelper.screens.HomeScreen import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen
import com.github.nacabaro.vbhelper.screens.ItemsScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.SettingsScreen import com.github.nacabaro.vbhelper.screens.SettingsScreen
@ -89,6 +90,11 @@ fun AppNavigation(
) )
} }
} }
composable(NavigationItems.Items.route) {
ItemsScreen(
navController = navController
)
}
} }
} }
} }

View File

@ -13,7 +13,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
@Composable @Composable
fun BottomNavigationBar(navController: NavController) { fun BottomNavigationBar(navController: NavController) {
val items = listOf( val items = listOf(
NavigationItems.Scan, NavigationItems.Items,
NavigationItems.Battles, NavigationItems.Battles,
NavigationItems.Home, NavigationItems.Home,
NavigationItems.Dex, NavigationItems.Dex,

View File

@ -15,4 +15,5 @@ sealed class NavigationItems (
object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings") object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings")
object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer") object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer")
object CardView : NavigationItems("Card/{dimId}", R.drawable.baseline_image_24, "Card") object CardView : NavigationItems("Card/{dimId}", R.drawable.baseline_image_24, "Card")
object Items : NavigationItems("Items", R.drawable.baseline_data_24, "Items")
} }

View File

@ -1,34 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems
@Composable
fun HomeScreen(
navController: NavController
) {
Scaffold (
topBar = {
TopBanner(
text = "VB Helper",
onGearClick = {
navController.navigate(NavigationItems.Settings.route)
}
)
}
) { contentPadding ->
Box (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
Text("Home Screen")
}
}
}

View File

@ -0,0 +1,12 @@
package com.github.nacabaro.vbhelper.screens
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
@Composable
fun ItemsScreen(
navController: NavController
) {
Text(text = "Items")
}

View File

@ -81,6 +81,7 @@ fun SpriteViewer(
contentDescription = "Sprite", contentDescription = "Sprite",
modifier = Modifier modifier = Modifier
.size(256.dp) .size(256.dp)
.padding(8.dp)
) )
} }
} }

View File

@ -14,6 +14,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@ -41,7 +42,9 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable @Composable
@ -62,8 +65,6 @@ fun StorageScreen(
} }
} }
Log.d("StorageScreen", "monList: $monList")
Scaffold ( Scaffold (
topBar = { TopBanner(text = "My Digimon") } topBar = { TopBanner(text = "My Digimon") }
) { contentPadding -> ) { contentPadding ->
@ -72,7 +73,7 @@ fun StorageScreen(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
modifier = Modifier modifier = Modifier
.padding(contentPadding) .padding(top = contentPadding.calculateTopPadding())
.fillMaxSize() .fillMaxSize()
) { ) {
Text( Text(
@ -96,9 +97,6 @@ fun StorageScreen(
width = index.spriteWidth, width = index.spriteWidth,
height = index.spriteHeight height = index.spriteHeight
), ),
modifier = Modifier
.padding(8.dp)
.size(96.dp),
onClick = { onClick = {
selectedCharacter = index.id selectedCharacter = index.id
} }
@ -108,6 +106,15 @@ fun StorageScreen(
StorageDialog( StorageDialog(
characterId = selectedCharacter!!, characterId = selectedCharacter!!,
onDismissRequest = { selectedCharacter = null }, onDismissRequest = { selectedCharacter = null },
onClickSetActive = {
coroutineScope.launch {
withContext(Dispatchers.IO) {
storageRepository.setActiveCharacter(selectedCharacter!!)
selectedCharacter = null
}
navController.navigate(NavigationItems.Home.route)
}
},
onSendToBracelet = { onSendToBracelet = {
navController.navigate( navController.navigate(
NavigationItems.Scan.route.replace( NavigationItems.Scan.route.replace(
@ -127,7 +134,8 @@ fun StorageScreen(
fun StorageDialog( fun StorageDialog(
characterId: Long, characterId: Long,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onSendToBracelet: () -> Unit onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
@ -162,12 +170,20 @@ fun StorageDialog(
.padding(8.dp) .padding(8.dp)
) )
} }
Row { Row (
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
Button( Button(
onClick = onSendToBracelet onClick = onSendToBracelet
) { ) {
Text(text = "Send to bracelet") Text(text = "Send to bracelet")
} }
Button(
onClick = onClickSetActive
) {
Text(text = "Set active")
}
Button( Button(
onClick = onDismissRequest onClick = onDismissRequest
) { ) {

View File

@ -0,0 +1,201 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.ItemDisplay
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.utils.BitmapData
import java.util.Locale
@Composable
fun BEBEmHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
beData: BECharacterData,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues
) {
Column(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.verticalScroll(state = rememberScrollState())
) {
Row (
modifier = Modifier
.fillMaxWidth()
) {
CharacterEntry(
icon = BitmapData(
bitmap = activeMon.spriteIdle,
width = activeMon.spriteWidth,
height = activeMon.spriteHeight
),
multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
)
Column (
modifier = Modifier
.weight(0.5f)
.aspectRatio(0.5f)
) {
ItemDisplay(
icon = R.drawable.baseline_vitals_24,
textValue = activeMon.vitalPoints.toString(),
definition = "Vitals",
modifier = Modifier
.weight(0.5f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_trophy_24,
textValue = activeMon.trophies.toString(),
definition = "Trophies",
modifier = Modifier
.weight(0.5f)
.aspectRatio(1f)
.padding(8.dp)
)
}
}
Row (
modifier = Modifier
.fillMaxWidth()
) {
ItemDisplay(
icon = R.drawable.baseline_mood_24,
textValue = activeMon.mood.toString(),
definition = "Mood",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
val timeInHours = (beData.remainingTrainingTimeInMinutes / 60)
ItemDisplay(
icon = R.drawable.baseline_timer_24,
textValue = "$timeInHours h",
definition = "Training limit",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_rank_24,
textValue = beData.rank.toString(),
definition = "Rank",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
}
Row (
modifier = Modifier
.fillMaxWidth()
) {
val transformationCountdownInHours = activeMon.transformationCountdown / 60
ItemDisplay(
icon = R.drawable.baseline_next_24,
textValue = when (transformationCountdownInHours) {
0 -> "${activeMon.transformationCountdown} m"
else -> "$transformationCountdownInHours h"
},
definition = "Next timer",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_swords_24,
textValue = when {
activeMon.totalBattlesLost == 0 -> "0.00 %"
else -> {
val battleWinPercentage = activeMon.totalBattlesWon.toFloat() / (activeMon.totalBattlesWon + activeMon.totalBattlesLost).toFloat()
String.format(Locale.getDefault(), "%.2f", battleWinPercentage * 100) + " %" // Specify locale
}
},
definition = "Total battle win %",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_swords_24,
textValue = when {
activeMon.totalBattlesLost == 0 -> "0.00 %"
else -> {
val battleWinPercentage = activeMon.currentPhaseBattlesWon.toFloat() / (activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost).toFloat()
String.format(Locale.getDefault(), "%.2f", battleWinPercentage * 100) + " %" // Specify locale
}
},
definition = "Current phase win %",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
}
Row (
modifier = Modifier
.fillMaxWidth()
) {
TransformationHistoryCard(
transformationHistory = transformationHistory,
modifier = Modifier
.weight(1f)
.padding(8.dp)
)
}
Row (
modifier = Modifier
.fillMaxWidth()
) {
ItemDisplay(
icon = R.drawable.baseline_health_24,
textValue = "+${beData.trainingHp}",
definition = "Training HP",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_agility_24,
textValue = "+${beData.trainingBp}",
definition = "Training BP",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_attack_24,
textValue = "+${beData.trainingAp}",
definition = "Training AP",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
}
}
}

View File

@ -0,0 +1,171 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import java.util.Locale
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.ItemDisplay
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlin.text.format
@Composable
fun BEDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
beData: BECharacterData,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues
) {
Column(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.verticalScroll(state = rememberScrollState())
) {
Row (
modifier = Modifier
.fillMaxWidth()
) {
CharacterEntry(
icon = BitmapData(
bitmap = activeMon.spriteIdle,
width = activeMon.spriteWidth,
height = activeMon.spriteHeight
),
multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
)
Column (
modifier = Modifier
.weight(0.5f)
.aspectRatio(0.5f)
) {
ItemDisplay(
icon = R.drawable.baseline_vitals_24,
textValue = activeMon.vitalPoints.toString(),
definition = "Vitals",
modifier = Modifier
.weight(0.5f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_trophy_24,
textValue = activeMon.trophies.toString(),
definition = "Trophies",
modifier = Modifier
.weight(0.5f)
.aspectRatio(1f)
.padding(8.dp)
)
}
}
Row (
modifier = Modifier
.fillMaxWidth()
) {
ItemDisplay(
icon = R.drawable.baseline_mood_24,
textValue = activeMon.mood.toString(),
definition = "Mood",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
val timeInHours = (beData.remainingTrainingTimeInMinutes / 60)
ItemDisplay(
icon = R.drawable.baseline_timer_24,
textValue = "$timeInHours h",
definition = "Training limit",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
// Maybe get rid of this?
ItemDisplay(
icon = R.drawable.baseline_rank_24,
textValue = beData.rank.toString(),
definition = "Rank",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
}
Row (
modifier = Modifier
.fillMaxWidth()
) {
val transformationCountdownInHours = activeMon.transformationCountdown / 60
ItemDisplay(
icon = R.drawable.baseline_next_24,
textValue = when (transformationCountdownInHours) {
0 -> "${activeMon.transformationCountdown} m"
else -> "$transformationCountdownInHours h"
},
definition = "Next timer",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_swords_24,
textValue = when {
activeMon.totalBattlesLost == 0 -> "0.00 %"
else -> {
val battleWinPercentage = activeMon.totalBattlesWon.toFloat() / (activeMon.totalBattlesWon + activeMon.totalBattlesLost).toFloat()
String.format(Locale.getDefault(), "%.2f", battleWinPercentage * 100) + " %" // Specify locale
}
},
definition = "Total battle win %",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_swords_24,
textValue = when {
activeMon.totalBattlesLost == 0 -> "0.00 %"
else -> {
val battleWinPercentage = activeMon.currentPhaseBattlesWon.toFloat() / (activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost).toFloat()
String.format(Locale.getDefault(), "%.2f", battleWinPercentage * 100) + " %" // Specify locale
}
},
definition = "Current phase win %",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
}
Row (
modifier = Modifier
.fillMaxWidth()
) {
TransformationHistoryCard(
transformationHistory = transformationHistory,
modifier = Modifier
.weight(1f)
.padding(8.dp)
)
}
}
}

View File

@ -0,0 +1,102 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.DeviceType
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun HomeScreen(
navController: NavController
) {
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val activeMon = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
val beData = remember { mutableStateOf<BECharacterData?>(null) }
val vbData = remember { mutableStateOf<VBCharacterData?>(null) }
LaunchedEffect(storageRepository, activeMon) {
withContext(Dispatchers.IO) {
activeMon.value = storageRepository.getActiveCharacter()
if (activeMon.value != null) {
beData.value = storageRepository.getCharacterBeData(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
}
}
}
Scaffold (
topBar = {
TopBanner(
text = "VB Helper",
onScanClick = {
navController.navigate(NavigationItems.Scan.route)
},
onGearClick = {
navController.navigate(NavigationItems.Settings.route)
}
)
}
) { contentPadding ->
if (activeMon.value == null || (beData.value == null && vbData.value == null) || transformationHistory.value == null) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(top = contentPadding.calculateTopPadding())
) {
Text(text = "Nothing to see here")
}
} else {
if (activeMon.value!!.isBemCard) {
Log.d("HomeScreen", "BEDeviceBEm")
BEBEmHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
)
} else if (!activeMon.value!!.isBemCard && activeMon.value!!.characterType == DeviceType.BEDevice) {
Log.d("HomeScreen", "BEDevice")
BEDiMHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
)
} else {
VBDiMHomeScreen(
activeMon = activeMon.value!!,
vbData = vbData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
)
}
}
}
}

View File

@ -0,0 +1,16 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@Composable
fun VBDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
vbData: VBCharacterData,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues
) {
TODO("Not implemented yet")
}

View File

@ -61,7 +61,7 @@ fun ScanScreen(
val context = LocalContext.current val context = LocalContext.current
LaunchedEffect(storageRepository) { LaunchedEffect(storageRepository) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
if(characterId != null) { if(characterId != null && nfcCharacter == null) {
nfcCharacter = characterToNfc(context, characterId) nfcCharacter = characterToNfc(context, characterId)
} }
} }
@ -129,6 +129,12 @@ fun ScanScreen(
} else if (isDoneSendingCard && isDoneWritingCharacter) { } else if (isDoneSendingCard && isDoneWritingCharacter) {
writingScreen = false writingScreen = false
navController.navigate(NavigationItems.Home.route) navController.navigate(NavigationItems.Home.route)
LaunchedEffect(storageRepository) {
withContext(Dispatchers.IO) {
storageRepository
.deleteCharacter(characterId!!)
}
}
} }
if (readingScreen) { if (readingScreen) {

View File

@ -21,11 +21,24 @@ class StorageRepository (
return db.userCharacterDao().getBeData(id) return db.userCharacterDao().getBeData(id)
} }
fun getTransformationHistory(characterId: Long): List<TransformationHistory> { fun getTransformationHistory(characterId: Long): List<CharacterDtos.TransformationHistory>? {
return db.userCharacterDao().getTransformationHistory(characterId) return db.userCharacterDao().getTransformationHistory(characterId)
} }
suspend fun getCharacterData(id: Long): CharacterDtos.DiMInfo { suspend fun getCharacterData(id: Long): CharacterDtos.DiMInfo {
return db.characterDao().getCharacterInfo(id) return db.characterDao().getCharacterInfo(id)
} }
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? {
return db.userCharacterDao().getActiveCharacter()
}
fun deleteCharacter(id: Long) {
return db.userCharacterDao().deleteCharacterById(id)
}
fun setActiveCharacter(id: Long) {
db.userCharacterDao().clearActiveCharacter()
return db.userCharacterDao().setActiveCharacter(id)
}
} }

View File

@ -1,12 +1,15 @@
package com.github.nacabaro.vbhelper.utils package com.github.nacabaro.vbhelper.utils
import android.content.Context import android.content.Context
import android.icu.util.Calendar
import android.icu.util.GregorianCalendar
import com.github.cfogrady.vbnfc.be.BENfcCharacter 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
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.DeviceType import com.github.nacabaro.vbhelper.domain.DeviceType
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import java.util.Date
suspend fun characterToNfc(context: Context, characterId: Long): NfcCharacter? { suspend fun characterToNfc(context: Context, characterId: Long): NfcCharacter? {
val app = context.applicationContext as VBHelper val app = context.applicationContext as VBHelper
@ -18,18 +21,27 @@ suspend fun characterToNfc(context: Context, characterId: Long): NfcCharacter? {
if (userCharacter.characterType == DeviceType.BEDevice) { if (userCharacter.characterType == DeviceType.BEDevice) {
val beData = storageRepository.getCharacterBeData(characterId) val beData = storageRepository.getCharacterBeData(characterId)
val transformationHistory = storageRepository val transformationHistory = storageRepository
.getTransformationHistory(characterId) .getTransformationHistory(characterId)!!
.map { .map {
val date = Date(it.transformationDate)
val calendar = GregorianCalendar()
calendar.time = date
NfcCharacter.Transformation( NfcCharacter.Transformation(
toCharIndex = it.toCharIndex.toUByte(), toCharIndex = it.monIndex.toUByte(),
year = it.year.toUShort(), year = calendar
month = it.month.toUByte(), .get(Calendar.YEAR)
day = it.day.toUByte() .toUShort(),
month = calendar
.get(Calendar.MONTH)
.toUByte(),
day = calendar
.get(Calendar.DAY_OF_MONTH)
.toUByte()
) )
}.toTypedArray() }.toTypedArray()
// Maybe this is the issue? val paddedTransformationArray = padTransformationArray(transformationHistory)
val dummyVitalHistory = arrayOf<NfcCharacter.DailyVitals>()
val nfcData = BENfcCharacter( val nfcData = BENfcCharacter(
dimId = characterInfo.cardId.toUShort(), dimId = characterInfo.cardId.toUShort(),
@ -55,7 +67,7 @@ suspend fun characterToNfc(context: Context, characterId: Long): NfcCharacter? {
totalBattlesLost = userCharacter.totalBattlesLost.toUShort(), totalBattlesLost = userCharacter.totalBattlesLost.toUShort(),
activityLevel = userCharacter.activityLevel.toByte(), activityLevel = userCharacter.activityLevel.toByte(),
heartRateCurrent = userCharacter.heartRateCurrent.toUByte(), heartRateCurrent = userCharacter.heartRateCurrent.toUByte(),
transformationHistory = transformationHistory, transformationHistory = paddedTransformationArray,
vitalHistory = Array(7) { vitalHistory = Array(7) {
NfcCharacter.DailyVitals(0u, 0u, 0u, 0u) NfcCharacter.DailyVitals(0u, 0u, 0u, 0u)
}, },

View File

@ -0,0 +1,23 @@
package com.github.nacabaro.vbhelper.utils
import com.github.cfogrady.vbnfc.data.NfcCharacter
fun padTransformationArray(
transformationArray: Array<NfcCharacter.Transformation>
): Array<NfcCharacter.Transformation> {
if (transformationArray.size >= 8) {
return transformationArray
}
val paddedArray = Array(8) {
NfcCharacter.Transformation(
toCharIndex = 255u,
year = 65535u,
month = 255u,
day = 255u
)
}
System.arraycopy(transformationArray, 0, paddedArray, 0, transformationArray.size)
return paddedArray
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M160,880q-17,0 -28.5,-11.5T120,840v-200q0,-33 23.5,-56.5T200,560v-160q0,-33 23.5,-56.5T280,320h160v-58q-18,-12 -29,-29t-11,-41q0,-15 6,-29.5t18,-26.5l56,-56 56,56q12,12 18,26.5t6,29.5q0,24 -11,41t-29,29v58h160q33,0 56.5,23.5T760,400v160q33,0 56.5,23.5T840,640v200q0,17 -11.5,28.5T800,880L160,880ZM280,560h400v-160L280,400v160ZM200,800h560v-160L200,640v160ZM280,560h400,-400ZM200,800h560,-560ZM760,560L200,560h560Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M520,920v-240l-84,-80 -40,176 -276,-56 16,-80 192,40 64,-324 -72,28v136h-80v-188l158,-68q35,-15 51.5,-19.5T480,240q21,0 39,11t29,29l40,64q26,42 70.5,69T760,440v80q-66,0 -123.5,-27.5T540,420l-24,120 84,80v300h-80ZM540,220q-33,0 -56.5,-23.5T460,140q0,-33 23.5,-56.5T540,60q33,0 56.5,23.5T620,140q0,33 -23.5,56.5T540,220Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M57,880 L1,824l146,-146 -44,-118q-7,-18 -3,-41.5t23,-42.5l132,-132q12,-12 26,-18t31,-6q17,0 31,6t26,18l80,78q27,27 66,42.5t84,15.5v80q-60,0 -112,-19t-90,-57l-28,-28 -94,94 84,86v244h-80v-210l-52,-48v88L57,880ZM599,880v-280l84,-80 -24,-140q-15,18 -33,32t-39,26q-33,-2 -62.5,-14T475,392q45,-8 79.5,-30.5T611,304l40,-64q17,-27 47,-36.5t59,2.5l202,86v188h-80v-136l-72,-28L919,880h-84l-72,-300 -84,80v220h-80ZM459,340q-33,0 -56.5,-23.5T379,260q0,-33 23.5,-56.5T459,180q33,0 56.5,23.5T539,260q0,33 -23.5,56.5T459,340ZM659,180q-33,0 -56.5,-23.5T579,100q0,-33 23.5,-56.5T659,20q33,0 56.5,23.5T739,100q0,33 -23.5,56.5T659,180Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M440,777v-274L200,364v274l240,139ZM520,777 L760,638v-274L520,503v274ZM480,434 L717,297 480,160 243,297 480,434ZM160,708q-19,-11 -29.5,-29T120,639v-318q0,-22 10.5,-40t29.5,-29l280,-161q19,-11 40,-11t40,11l280,161q19,11 29.5,29t10.5,40v318q0,22 -10.5,40T800,708L520,869q-19,11 -40,11t-40,-11L160,708ZM480,480Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M160,520v-80h640v80L160,520Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m480,840 l-58,-52q-101,-91 -167,-157T150,512.5Q111,460 95.5,416T80,326q0,-94 63,-157t157,-63q52,0 99,22t81,62q34,-40 81,-62t99,-22q94,0 157,63t63,157q0,46 -15.5,90T810,512.5Q771,565 705,631T538,788l-58,52ZM480,732q96,-86 158,-147.5t98,-107q36,-45.5 50,-81t14,-70.5q0,-60 -40,-100t-100,-40q-47,0 -87,26.5T518,280h-76q-15,-41 -55,-67.5T300,186q-60,0 -100,40t-40,100q0,35 14,70.5t50,81q36,45.5 98,107T480,732ZM480,459Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M620,440q25,0 42.5,-17.5T680,380q0,-25 -17.5,-42.5T620,320q-25,0 -42.5,17.5T560,380q0,25 17.5,42.5T620,440ZM340,440q25,0 42.5,-17.5T400,380q0,-25 -17.5,-42.5T340,320q-25,0 -42.5,17.5T280,380q0,25 17.5,42.5T340,440ZM480,700q68,0 123.5,-38.5T684,560L276,560q25,63 80.5,101.5T480,700ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,480ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M660,720v-480h80v480h-80ZM220,720v-480l360,240 -360,240ZM300,480ZM300,570 L436,480 300,390v180Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m354,673 l126,-76 126,77 -33,-144 111,-96 -146,-13 -58,-136 -58,135 -146,13 111,97 -33,143ZM233,840l65,-281L80,370l288,-25 112,-265 112,265 288,25 -218,189 65,281 -247,-149 -247,149ZM480,490Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M360,120v-80h240v80L360,120ZM440,560h80v-240h-80v240ZM480,880q-74,0 -139.5,-28.5T226,774q-49,-49 -77.5,-114.5T120,520q0,-74 28.5,-139.5T226,266q49,-49 114.5,-77.5T480,160q62,0 119,20t107,58l56,-56 56,56 -56,56q38,50 58,107t20,119q0,74 -28.5,139.5T734,774q-49,49 -114.5,77.5T480,880ZM480,800q116,0 198,-82t82,-198q0,-116 -82,-198t-198,-82q-116,0 -198,82t-82,198q0,116 82,198t198,82ZM480,520Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M280,840v-80h160v-124q-49,-11 -87.5,-41.5T296,518q-75,-9 -125.5,-65.5T120,320v-40q0,-33 23.5,-56.5T200,200h80v-80h400v80h80q33,0 56.5,23.5T840,280v40q0,76 -50.5,132.5T664,518q-18,46 -56.5,76.5T520,636v124h160v80L280,840ZM280,432v-152h-80v40q0,38 22,68.5t58,43.5ZM480,560q50,0 85,-35t35,-85v-240L360,200v240q0,50 35,85t85,35ZM680,432q36,-13 58,-43.5t22,-68.5v-40h-80v152ZM480,380Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M156,447q-11,-12 -11,-28.5t11,-28.5l112,-112 -43,-43 -12,12q-12,12 -28.5,12T156,247q-11,-11 -11,-28t11,-28l80,-80q12,-12 28.5,-12t28.5,12q11,11 11,28t-11,28l-12,12 43,43 112,-112q12,-12 28.5,-12t28.5,12q12,12 12,28.5T493,167l-27,26 295,295q23,23 23,56.5T761,601l-28,29 189,188L808,818L676,686l-28,29q-23,23 -56.5,23T535,715L240,420l-27,27q-12,11 -28.5,11T156,447ZM296,364 L591,659 704,545 644,484 588,540q-12,11 -28.5,11.5T532,541q-12,-12 -12,-28.5t12,-28.5l56,-56 -60,-60 -56,56q-12,11 -28.5,11T415,424q-11,-12 -11,-28.5t11,-28.5l56,-56 -61,-61 -114,114ZM296,364 L410,250 296,364Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M600,880q-17,0 -28.5,-11.5T560,840v-43q-23,-4 -43.5,-11.5T478,765l-40,40q-12,11 -28.5,11.5T381,805q-12,-12 -12,-28.5t12,-28.5l41,-41q-3,-5 -6,-10.5t-6,-10.5l-27,-53 -49,49q-12,11 -28,11.5T278,682q-12,-12 -12,-28t12,-28l49,-50 -53,-26q-5,-2 -9,-4.5t-9,-5.5l-36,36q-12,11 -28.5,11.5T163,576q-12,-12 -12,-28t12,-28l35,-35q-14,-19 -22.5,-40.5T163,400h-43q-17,0 -28.5,-11.5T80,360q0,-17 11.5,-28.5T120,320h45q5,-19 12,-36t18,-33l-35,-35q-12,-12 -12,-28t12,-28q12,-12 28,-12t28,12l35,35q16,-11 33,-18t36,-12v-45q0,-17 11.5,-28.5T360,80q17,0 28.5,11.5T400,120v43q24,4 45.5,13t40.5,23l35,-35q12,-12 28.5,-12t28.5,12q12,12 12,28t-12,28l-37,37q2,4 4.5,8t4.5,9l25,50 46,-46q12,-12 28.5,-12t28.5,12q12,12 12,28.5T678,335l-48,47 56,28q6,3 12.5,6.5T710,424l40,-40q12,-12 28,-12t28,12q12,12 12,28.5T806,441l-40,39q12,18 19.5,38t11.5,42h43q17,0 28.5,11.5T880,600q0,17 -11.5,28.5T840,640h-45q-5,19 -12,35.5T765,708l34,34q12,12 12,28.5T799,799q-12,11 -28.5,11.5T742,799l-33,-34q-16,11 -33,18t-36,12v45q0,17 -11.5,28.5T600,880ZM594,720q58,0 95.5,-44T718,574q-5,-30 -22.5,-54T650,482l-66,-34q-23,-12 -41.5,-30.5T512,376l-34,-66q-16,-32 -46,-51t-66,-19q-58,0 -95.5,44T242,386q5,30 22.5,54t45.5,38l66,34q23,12 41.5,30.5T448,584l34,66q16,32 46,51t66,19ZM380,420q25,0 42.5,-17.5T440,360q0,-25 -17.5,-42.5T380,300q-25,0 -42.5,17.5T320,360q0,25 17.5,42.5T380,420ZM580,670q21,0 35.5,-14.5T630,620q0,-21 -14.5,-35.5T580,570q-21,0 -35.5,14.5T530,620q0,21 14.5,35.5T580,670ZM480,480Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M300,120q52,0 99,22t81,62q34,-40 81,-62t99,-22q94,0 157,63t63,157q0,5 -0.5,10t-0.5,10h-80q1,-5 1,-10v-10q0,-60 -40,-100t-100,-40q-47,0 -87,26.5T518,294h-76q-15,-41 -55,-67.5T300,200q-60,0 -100,40t-40,100v10q0,5 1,10L81,360q0,-5 -0.5,-10t-0.5,-10q0,-94 63,-157t157,-63ZM212,600h112q32,31 70,67t86,79q48,-43 86,-79t70,-67h113q-38,42 -90,91T538,802l-58,52 -58,-52q-69,-62 -120.5,-111T212,600ZM442,640q13,0 22.5,-7.5T478,613l54,-163 35,52q5,8 14,13t19,5h320v-80L623,440l-69,-102q-6,-9 -15.5,-13.5T518,320q-13,0 -22.5,7.5T482,347l-54,162 -34,-51q-5,-8 -14,-13t-19,-5L40,440v80h297l69,102q6,9 15.5,13.5T442,640ZM480,473Z"
android:fillColor="#000000"/>
</vector>