A few things, again...

- Added ability to write character (incomplete)
- Renamed BottomNavItem.kt to NavigationItems.kt, as it covers the entire application navigation, not just the bottom navigation bar
- Modified the ScanScreenController and the Impl to accomodate writing characters on the BE (need to do the VB later on)
- Modified repositories to fetch data from the database and additional information needed to convert back to NfcCharacter
- Function to convert to NfcCharacter

Originally this was also going to cover the home screen, since my idea was to have marked as active (the one shown in the home screen) be the one sent to the watch, but for testing I have added a "send to bracelet" button on the pop-up on the storage screen.
This commit is contained in:
Nacho 2025-01-12 00:57:34 +01:00
parent 7f22650601
commit cac5198488
16 changed files with 343 additions and 73 deletions

View File

@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.Query 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
@Dao @Dao
interface CharacterDao { interface CharacterDao {
@ -25,4 +26,15 @@ interface CharacterDao {
@Query("SELECT * FROM Sprites") @Query("SELECT * FROM Sprites")
suspend fun getAllSprites(): List<Sprites> suspend fun getAllSprites(): List<Sprites>
@Query("""
SELECT
d.dimId as cardId,
c.monIndex as charId
FROM Character c
JOIN UserCharacter uc ON c.id = uc.charId
JOIN Dim d ON c.dimId = d.id
WHERE uc.id = :charId
""")
suspend fun getCharacterInfo(charId: Long): CharacterDtos.DiMInfo
} }

View File

@ -21,7 +21,7 @@ interface UserCharacterDao {
fun insertTransformationHistory(vararg transformationHistory: TransformationHistory) fun insertTransformationHistory(vararg transformationHistory: TransformationHistory)
@Query("SELECT * FROM TransformationHistory WHERE monId = :monId") @Query("SELECT * FROM TransformationHistory WHERE monId = :monId")
fun getTransformationHistory(monId: Int): List<TransformationHistory> fun getTransformationHistory(monId: Long): List<TransformationHistory>
@Query(""" @Query("""
SELECT SELECT
@ -36,4 +36,7 @@ interface UserCharacterDao {
@Query("SELECT * FROM UserCharacter WHERE id = :id") @Query("SELECT * FROM UserCharacter WHERE id = :id")
suspend fun getCharacter(id: Long): UserCharacter suspend fun getCharacter(id: Long): UserCharacter
@Query("SELECT * FROM BECharacterData WHERE id = :id")
suspend fun getBeData(id: Long): BECharacterData
} }

View File

@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.dtos package com.github.nacabaro.vbhelper.dtos
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.domain.DeviceType import com.github.nacabaro.vbhelper.domain.DeviceType
@ -29,4 +28,9 @@ object CharacterDtos {
val spriteWidth: Int, val spriteWidth: Int,
val spriteHeight: Int val spriteHeight: Int
) )
data class DiMInfo(
val cardId: Int,
val charId: Int
)
} }

View File

@ -35,45 +35,51 @@ fun AppNavigation(
) { contentPadding -> ) { contentPadding ->
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = BottomNavItem.Home.route, startDestination = NavigationItems.Home.route,
modifier = Modifier modifier = Modifier
.padding(contentPadding) .padding(contentPadding)
) { ) {
composable(BottomNavItem.Battles.route) { composable(NavigationItems.Battles.route) {
BattlesScreen() BattlesScreen()
} }
composable(BottomNavItem.Home.route) { composable(NavigationItems.Home.route) {
HomeScreen( HomeScreen(
navController = navController navController = navController
) )
} }
composable(BottomNavItem.Storage.route) { composable(NavigationItems.Storage.route) {
StorageScreen() StorageScreen(
navController = navController
)
} }
composable(BottomNavItem.Scan.route) { composable(NavigationItems.Scan.route) {
val characterIdString = it.arguments?.getString("characterId")
val characterId = characterIdString?.toLongOrNull()
ScanScreen( ScanScreen(
navController = navController, navController = navController,
scanScreenController = applicationNavigationHandlers.scanScreenController, scanScreenController = applicationNavigationHandlers.scanScreenController,
characterId = characterId
) )
} }
composable(BottomNavItem.Dex.route) { composable(NavigationItems.Dex.route) {
DexScreen( DexScreen(
navController = navController navController = navController
) )
} }
composable(BottomNavItem.Settings.route) { composable(NavigationItems.Settings.route) {
SettingsScreen( SettingsScreen(
navController = navController, navController = navController,
settingsScreenController = applicationNavigationHandlers.settingsScreenController, settingsScreenController = applicationNavigationHandlers.settingsScreenController,
onClickImportCard = onClickImportCard onClickImportCard = onClickImportCard
) )
} }
composable(BottomNavItem.Viewer.route) { composable(NavigationItems.Viewer.route) {
SpriteViewer( SpriteViewer(
navController = navController navController = navController
) )
} }
composable(BottomNavItem.CardView.route) { composable(NavigationItems.CardView.route) {
val dimId = it.arguments?.getString("dimId") val dimId = it.arguments?.getString("dimId")
Log.d("dimId", dimId.toString()) Log.d("dimId", dimId.toString())
if (dimId != null) { if (dimId != null) {

View File

@ -1,18 +0,0 @@
package com.github.nacabaro.vbhelper.navigation
import com.github.nacabaro.vbhelper.R
sealed class BottomNavItem (
var route: String,
var icon: Int,
var label: String
) {
object Scan : BottomNavItem("Scan", R.drawable.baseline_nfc_24, "Scan")
object Battles : BottomNavItem("Battle", R.drawable.baseline_swords_24, "Battle")
object Home : BottomNavItem("Home", R.drawable.baseline_cottage_24, "Home")
object Dex : BottomNavItem("Dex", R.drawable.baseline_menu_book_24, "Dex")
object Storage : BottomNavItem("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
object Settings : BottomNavItem("Settings", R.drawable.baseline_settings_24, "Settings")
object Viewer : BottomNavItem("Viewer", R.drawable.baseline_image_24, "Viewer")
object CardView : BottomNavItem("Card/{dimId}", R.drawable.baseline_image_24, "Card")
}

View File

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

View File

@ -0,0 +1,18 @@
package com.github.nacabaro.vbhelper.navigation
import com.github.nacabaro.vbhelper.R
sealed class NavigationItems (
var route: String,
var icon: Int,
var label: String
) {
object Scan : NavigationItems("Scan/{characterId}", R.drawable.baseline_nfc_24, "Scan")
object Battles : NavigationItems("Battle", R.drawable.baseline_swords_24, "Battle")
object Home : NavigationItems("Home", R.drawable.baseline_cottage_24, "Home")
object Dex : NavigationItems("Dex", R.drawable.baseline_menu_book_24, "Dex")
object Storage : NavigationItems("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings")
object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer")
object CardView : NavigationItems("Card/{dimId}", R.drawable.baseline_image_24, "Card")
}

View File

@ -19,7 +19,7 @@ import com.github.nacabaro.vbhelper.components.DexDiMEntry
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.di.VBHelper
import com.github.nacabaro.vbhelper.domain.Dim import com.github.nacabaro.vbhelper.domain.Dim
import com.github.nacabaro.vbhelper.navigation.BottomNavItem import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.DexRepository import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -45,7 +45,7 @@ fun DexScreen(
TopBanner( TopBanner(
text = "Discovered Digimon", text = "Discovered Digimon",
onGearClick = { onGearClick = {
navController.navigate(BottomNavItem.Viewer.route) navController.navigate(NavigationItems.Viewer.route)
} }
) )
} }
@ -65,7 +65,7 @@ fun DexScreen(
onClick = { onClick = {
navController navController
.navigate( .navigate(
BottomNavItem NavigationItems
.CardView.route .CardView.route
.replace("{dimId}", "${it.id}") .replace("{dimId}", "${it.id}")
) )

View File

@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.BottomNavItem import com.github.nacabaro.vbhelper.navigation.NavigationItems
@Composable @Composable
fun HomeScreen( fun HomeScreen(
@ -19,7 +19,7 @@ fun HomeScreen(
TopBanner( TopBanner(
text = "VB Helper", text = "VB Helper",
onGearClick = { onGearClick = {
navController.navigate(BottomNavItem.Settings.route) navController.navigate(NavigationItems.Settings.route)
} }
) )
} }

View File

@ -1,11 +1,11 @@
package com.github.nacabaro.vbhelper.screens package com.github.nacabaro.vbhelper.screens
import android.util.Log import android.util.Log
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
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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
@ -21,11 +21,9 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -34,18 +32,22 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
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.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
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.launch import kotlinx.coroutines.launch
@Composable @Composable
fun StorageScreen() { fun StorageScreen(
navController: NavController
) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db) val storageRepository = StorageRepository(application.container.db)
@ -96,14 +98,24 @@ fun StorageScreen() {
), ),
modifier = Modifier modifier = Modifier
.padding(8.dp) .padding(8.dp)
.size(96.dp) .size(96.dp),
onClick = {
selectedCharacter = index.id
}
) )
if (selectedCharacter != null) { if (selectedCharacter != null) {
StorageDialog( StorageDialog(
characterId = selectedCharacter!!, characterId = selectedCharacter!!,
onDismissRequest = { selectedCharacter = null } onDismissRequest = { selectedCharacter = null },
onSendToBracelet = {
navController.navigate(
NavigationItems.Scan.route.replace(
"{characterId}",
selectedCharacter.toString()
)
)
}
) )
} }
} }
@ -114,7 +126,8 @@ fun StorageScreen() {
@Composable @Composable
fun StorageDialog( fun StorageDialog(
characterId: Long, characterId: Long,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit,
onSendToBracelet: () -> Unit
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
@ -149,10 +162,17 @@ fun StorageDialog(
.padding(8.dp) .padding(8.dp)
) )
} }
Button( Row {
onClick = onDismissRequest Button(
) { onClick = onSendToBracelet
Text(text = "Close") ) {
Text(text = "Send to bracelet")
}
Button(
onClick = onDismissRequest
) {
Text(text = "Close")
}
} }
} }
} }

View File

@ -15,11 +15,12 @@ import com.github.nacabaro.vbhelper.components.TopBanner
@Composable @Composable
fun ReadingCharacterScreen( fun ReadingCharacterScreen(
topBannerText: String,
onClickCancel: () -> Unit, onClickCancel: () -> Unit,
) { ) {
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner("Reading Character") TopBanner(topBannerText)
} }
) { innerPadding -> ) { innerPadding ->
Column ( Column (

View File

@ -12,6 +12,7 @@ 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.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -25,25 +26,48 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.BottomNavItem import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.source.isMissingSecrets import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets import com.github.nacabaro.vbhelper.source.proto.Secrets
import com.github.nacabaro.vbhelper.utils.characterToNfc
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext
const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER" const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER"
@Composable @Composable
fun ScanScreen( fun ScanScreen(
navController: NavController, navController: NavController,
characterId: Long?,
scanScreenController: ScanScreenController, scanScreenController: ScanScreenController,
) { ) {
val secrets by scanScreenController.secretsFlow.collectAsState(null) val secrets by scanScreenController.secretsFlow.collectAsState(null)
var readingScreen by remember { mutableStateOf(false) } var readingScreen by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) } var isDoneReadingCharacter by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
DisposableEffect(readingScreen) { val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) }
val context = LocalContext.current
LaunchedEffect(storageRepository) {
withContext(Dispatchers.IO) {
if(characterId != null) {
nfcCharacter = characterToNfc(context, characterId)
}
}
}
DisposableEffect(readingScreen || writingScreen) {
if(readingScreen) { if(readingScreen) {
scanScreenController.registerActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER, object: ActivityLifecycleListener { scanScreenController.registerActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER, object: ActivityLifecycleListener {
override fun onPause() { override fun onPause() {
@ -60,9 +84,39 @@ fun ScanScreen(
scanScreenController.onClickRead(secrets!!) { scanScreenController.onClickRead(secrets!!) {
isDoneReadingCharacter = true isDoneReadingCharacter = true
} }
} else if (writingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
}
)
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
} }
onDispose { onDispose {
if(readingScreen) { if(readingScreen || writingScreen) {
scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER) scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
scanScreenController.cancelRead() scanScreenController.cancelRead()
} }
@ -71,33 +125,68 @@ fun ScanScreen(
if (isDoneReadingCharacter) { if (isDoneReadingCharacter) {
readingScreen = false readingScreen = false
navController.navigate(BottomNavItem.Home.route) navController.navigate(NavigationItems.Home.route)
} else if (isDoneSendingCard && isDoneWritingCharacter) {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
} }
if (readingScreen) { if (readingScreen) {
ReadingCharacterScreen { ReadingCharacterScreen("Reading character") {
readingScreen = false readingScreen = false
scanScreenController.cancelRead() scanScreenController.cancelRead()
} }
} else if (writingScreen) {
if (!isDoneSendingCard) {
ReadingCharacterScreen("Sending card") {
isDoneSendingCard = true
scanScreenController.cancelRead()
}
} else if (!isDoneWritingCharacter) {
ReadingCharacterScreen("Writing character") {
isDoneWritingCharacter = true
writingScreen = false
scanScreenController.cancelRead()
}
}
} else { } else {
val context = LocalContext.current
ChooseConnectOption( ChooseConnectOption(
onClickRead = { onClickRead = when {
if(secrets == null) { characterId != null -> null
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show() else -> {
} else if(secrets?.isMissingSecrets() == true) { {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show() if(secrets == null) {
} else { Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
readingScreen = true // kicks off nfc adapter in DisposableEffect } else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
} else {
readingScreen = true // kicks off nfc adapter in DisposableEffect
}
}
} }
}, },
onClickWrite = when {
nfcCharacter == null -> null
else -> {
{
if(secrets == null) {
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
} else {
writingScreen = true // kicks off nfc adapter in DisposableEffect
}
}
}
}
) )
} }
} }
@Composable @Composable
private fun ChooseConnectOption( fun ChooseConnectOption(
onClickRead: () -> Unit, onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
) { ) {
Scaffold( Scaffold(
topBar = { TopBanner(text = "Scan a Vital Bracelet") } topBar = { TopBanner(text = "Scan a Vital Bracelet") }
@ -111,12 +200,14 @@ private fun ChooseConnectOption(
) { ) {
ScanButton( ScanButton(
text = "Vital Bracelet to App", text = "Vital Bracelet to App",
onClick = onClickRead, disabled = onClickRead == null,
onClick = onClickRead?: { },
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
ScanButton( ScanButton(
text = "App to Vital Bracelet", text = "App to Vital Bracelet",
onClick = {} disabled = onClickWrite == null,
onClick = onClickWrite?: { },
) )
} }
} }
@ -127,11 +218,13 @@ private fun ChooseConnectOption(
fun ScanButton( fun ScanButton(
text: String, text: String,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
disabled: Boolean = false,
) { ) {
Button( Button(
onClick = onClick, onClick = onClick,
modifier = modifier modifier = modifier,
enabled = !disabled,
) { ) {
Text( Text(
text = text, text = text,
@ -157,7 +250,10 @@ fun ScanScreenPreview() {
} }
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {} override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {}
override fun onClickCheckCard(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
override fun onClickWrite(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
override fun cancelRead() {} override fun cancelRead() {}
} },
characterId = null
) )
} }

View File

@ -1,5 +1,6 @@
package com.github.nacabaro.vbhelper.screens.scanScreen package com.github.nacabaro.vbhelper.screens.scanScreen
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.source.proto.Secrets import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -7,6 +8,9 @@ import kotlinx.coroutines.flow.Flow
interface ScanScreenController { interface ScanScreenController {
val secretsFlow: Flow<Secrets> val secretsFlow: Flow<Secrets>
fun onClickRead(secrets: Secrets, onComplete: ()->Unit) fun onClickRead(secrets: Secrets, onComplete: ()->Unit)
fun onClickCheckCard(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit)
fun onClickWrite(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit)
fun cancelRead() fun cancelRead()
fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener) fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener)

View File

@ -109,6 +109,30 @@ class ScanScreenControllerImpl(
} }
} }
override fun onClickWrite(
secrets: Secrets,
nfcCharacter: NfcCharacter,
onComplete: () -> Unit
) {
handleTag(secrets) { tagCommunicator ->
tagCommunicator.sendCharacter(nfcCharacter)
onComplete.invoke()
"Sent character successfully!"
}
}
override fun onClickCheckCard(
secrets: Secrets,
nfcCharacter: NfcCharacter,
onComplete: () -> Unit
) {
handleTag(secrets) { tagCommunicator ->
tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId)
onComplete.invoke()
"Sent DIM successfully!"
}
}
// EXTRACTED DIRECTLY FROM EXAMPLE APP // EXTRACTED DIRECTLY FROM EXAMPLE APP
private fun showWirelessSettings() { private fun showWirelessSettings() {
Toast.makeText(context, "NFC must be enabled", Toast.LENGTH_SHORT).show() Toast.makeText(context, "NFC must be enabled", Toast.LENGTH_SHORT).show()

View File

@ -1,6 +1,8 @@
package com.github.nacabaro.vbhelper.source package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
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.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@ -14,4 +16,16 @@ class StorageRepository (
suspend fun getSingleCharacter(id: Long): UserCharacter { suspend fun getSingleCharacter(id: Long): UserCharacter {
return db.userCharacterDao().getCharacter(id) return db.userCharacterDao().getCharacter(id)
} }
suspend fun getCharacterBeData(id: Long): BECharacterData {
return db.userCharacterDao().getBeData(id)
}
fun getTransformationHistory(characterId: Long): List<TransformationHistory> {
return db.userCharacterDao().getTransformationHistory(characterId)
}
suspend fun getCharacterData(id: Long): CharacterDtos.DiMInfo {
return db.characterDao().getCharacterInfo(id)
}
} }

View File

@ -0,0 +1,86 @@
package com.github.nacabaro.vbhelper.utils
import android.content.Context
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.be.FirmwareVersion
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.DeviceType
import com.github.nacabaro.vbhelper.source.StorageRepository
suspend fun characterToNfc(context: Context, characterId: Long): NfcCharacter? {
val app = context.applicationContext as VBHelper
val database = app.container.db
val storageRepository = StorageRepository(database)
val userCharacter = storageRepository.getSingleCharacter(characterId)
val characterInfo = storageRepository.getCharacterData(characterId)
if (userCharacter.characterType == DeviceType.BEDevice) {
val beData = storageRepository.getCharacterBeData(characterId)
val transformationHistory = storageRepository
.getTransformationHistory(characterId)
.map {
NfcCharacter.Transformation(
toCharIndex = it.toCharIndex.toUByte(),
year = it.year.toUShort(),
month = it.month.toUByte(),
day = it.day.toUByte()
)
}.toTypedArray()
// Maybe this is the issue?
val dummyVitalHistory = arrayOf<NfcCharacter.DailyVitals>()
val nfcData = BENfcCharacter(
dimId = characterInfo.cardId.toUShort(),
charIndex = characterInfo.charId.toUShort(),
stage = userCharacter.stage.toByte(),
attribute = userCharacter.attribute,
ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = userCharacter.nextAdventureMissionStage.toByte(),
mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(),
itemEffectMentalStateValue = beData.itemEffectMentalStateValue.toByte(),
itemEffectMentalStateMinutesRemaining = beData.itemEffectMentalStateMinutesRemaining.toByte(),
itemEffectActivityLevelValue = beData.itemEffectActivityLevelValue.toByte(),
itemEffectActivityLevelMinutesRemaining = beData.itemEffectActivityLevelMinutesRemaining.toByte(),
itemEffectVitalPointsChangeValue = beData.itemEffectVitalPointsChangeValue.toByte(),
itemEffectVitalPointsChangeMinutesRemaining = beData.itemEffectVitalPointsChangeMinutesRemaining.toByte(),
transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(),
injuryStatus = userCharacter.injuryStatus,
trainingPp = userCharacter.trophies.toUShort(),
currentPhaseBattlesWon = userCharacter.currentPhaseBattlesWon.toUShort(),
currentPhaseBattlesLost = userCharacter.currentPhaseBattlesLost.toUShort(),
totalBattlesWon = userCharacter.totalBattlesWon.toUShort(),
totalBattlesLost = userCharacter.totalBattlesLost.toUShort(),
activityLevel = userCharacter.activityLevel.toByte(),
heartRateCurrent = userCharacter.heartRateCurrent.toUByte(),
transformationHistory = transformationHistory,
vitalHistory = arrayOf(),
appReserved1 = byteArrayOf(),
appReserved2 = Array(2, { 0u }),
trainingHp = beData.trainingHp.toUShort(),
trainingAp = beData.trainingAp.toUShort(),
trainingBp = beData.trainingBp.toUShort(),
remainingTrainingTimeInMinutes = beData.remainingTrainingTimeInMinutes.toUShort(),
abilityRarity = beData.abilityRarity,
abilityType = beData.abilityType.toUShort(),
abilityBranch = beData.abilityBranch.toUShort(),
abilityReset = beData.abilityReset.toByte(),
rank = beData.rank.toByte(),
itemType = beData.itemType.toByte(),
itemMultiplier = beData.itemMultiplier.toByte(),
itemRemainingTime = beData.itemRemainingTime.toByte(),
otp0 = byteArrayOf(8),
otp1 = byteArrayOf(8),
characterCreationFirmwareVersion = FirmwareVersion(
minorVersion = beData.minorVersion.toByte(),
majorVersion = beData.majorVersion.toByte()
)
)
return nfcData
}
return null
}