~cytrogen/vbhelper

cac51984888b42b4e2a5f9c2abaabfb67c5b47f7 — Nacho 1 year, 3 months ago 7f22650
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.
M app/src/main/java/com/github/nacabaro/vbhelper/daos/CharacterDao.kt => app/src/main/java/com/github/nacabaro/vbhelper/daos/CharacterDao.kt +12 -0
@@ 5,6 5,7 @@ import androidx.room.Insert
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.Character
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.dtos.CharacterDtos

@Dao
interface CharacterDao {


@@ 25,4 26,15 @@ interface CharacterDao {

    @Query("SELECT * FROM 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
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/daos/UserCharacterDao.kt => app/src/main/java/com/github/nacabaro/vbhelper/daos/UserCharacterDao.kt +4 -1
@@ 21,7 21,7 @@ interface UserCharacterDao {
    fun insertTransformationHistory(vararg transformationHistory: TransformationHistory)

    @Query("SELECT * FROM TransformationHistory WHERE monId = :monId")
    fun getTransformationHistory(monId: Int): List<TransformationHistory>
    fun getTransformationHistory(monId: Long): List<TransformationHistory>

    @Query("""
        SELECT


@@ 36,4 36,7 @@ interface UserCharacterDao {

    @Query("SELECT * FROM UserCharacter WHERE id = :id")
    suspend fun getCharacter(id: Long): UserCharacter

    @Query("SELECT * FROM BECharacterData WHERE id = :id")
    suspend fun getBeData(id: Long): BECharacterData
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/dtos/CharacterDtos.kt => app/src/main/java/com/github/nacabaro/vbhelper/dtos/CharacterDtos.kt +5 -1
@@ 1,6 1,5 @@
package com.github.nacabaro.vbhelper.dtos

import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.domain.DeviceType



@@ 29,4 28,9 @@ object CharacterDtos {
        val spriteWidth: Int,
        val spriteHeight: Int
    )

    data class DiMInfo(
        val cardId: Int,
        val charId: Int
    )
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt => app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt +16 -10
@@ 35,45 35,51 @@ fun AppNavigation(
    ) { contentPadding ->
        NavHost(
            navController = navController,
            startDestination = BottomNavItem.Home.route,
            startDestination = NavigationItems.Home.route,
            modifier = Modifier
                .padding(contentPadding)
        ) {
            composable(BottomNavItem.Battles.route) {
            composable(NavigationItems.Battles.route) {
                BattlesScreen()
            }
            composable(BottomNavItem.Home.route) {
            composable(NavigationItems.Home.route) {
                HomeScreen(
                    navController = navController
                )
            }
            composable(BottomNavItem.Storage.route) {
                StorageScreen()
            composable(NavigationItems.Storage.route) {
                StorageScreen(
                    navController = navController
                )
            }
            composable(BottomNavItem.Scan.route) {
            composable(NavigationItems.Scan.route) {
                val characterIdString = it.arguments?.getString("characterId")
                val characterId = characterIdString?.toLongOrNull()

                ScanScreen(
                    navController = navController,
                    scanScreenController = applicationNavigationHandlers.scanScreenController,
                    characterId = characterId
                )
            }
            composable(BottomNavItem.Dex.route) {
            composable(NavigationItems.Dex.route) {
                DexScreen(
                    navController = navController
                )
            }
            composable(BottomNavItem.Settings.route) {
            composable(NavigationItems.Settings.route) {
                SettingsScreen(
                    navController = navController,
                    settingsScreenController = applicationNavigationHandlers.settingsScreenController,
                    onClickImportCard = onClickImportCard
                )
            }
            composable(BottomNavItem.Viewer.route) {
            composable(NavigationItems.Viewer.route) {
                SpriteViewer(
                    navController = navController
                )
            }
            composable(BottomNavItem.CardView.route) {
            composable(NavigationItems.CardView.route) {
                val dimId = it.arguments?.getString("dimId")
                Log.d("dimId", dimId.toString())
                if (dimId != null) {

D app/src/main/java/com/github/nacabaro/vbhelper/navigation/BottomNavItem.kt => app/src/main/java/com/github/nacabaro/vbhelper/navigation/BottomNavItem.kt +0 -18
@@ 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")
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/navigation/BottomNavigationBar.kt => app/src/main/java/com/github/nacabaro/vbhelper/navigation/BottomNavigationBar.kt +5 -5
@@ 13,11 13,11 @@ import androidx.navigation.compose.currentBackStackEntryAsState
@Composable
fun BottomNavigationBar(navController: NavController) {
    val items = listOf(
        BottomNavItem.Scan,
        BottomNavItem.Battles,
        BottomNavItem.Home,
        BottomNavItem.Dex,
        BottomNavItem.Storage,
        NavigationItems.Scan,
        NavigationItems.Battles,
        NavigationItems.Home,
        NavigationItems.Dex,
        NavigationItems.Storage,
    )
    NavigationBar {
        val currentBackStackEntry = navController.currentBackStackEntryAsState()

A app/src/main/java/com/github/nacabaro/vbhelper/navigation/NavigationItems.kt => app/src/main/java/com/github/nacabaro/vbhelper/navigation/NavigationItems.kt +18 -0
@@ 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")
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/screens/DexScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/DexScreen.kt +3 -3
@@ 19,7 19,7 @@ import com.github.nacabaro.vbhelper.components.DexDiMEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
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 kotlinx.coroutines.launch



@@ 45,7 45,7 @@ fun DexScreen(
            TopBanner(
                text = "Discovered Digimon",
                onGearClick = {
                    navController.navigate(BottomNavItem.Viewer.route)
                    navController.navigate(NavigationItems.Viewer.route)
                }
            )
        }


@@ 65,7 65,7 @@ fun DexScreen(
                    onClick = {
                        navController
                            .navigate(
                                BottomNavItem
                                NavigationItems
                                    .CardView.route
                                    .replace("{dimId}", "${it.id}")
                            )

M app/src/main/java/com/github/nacabaro/vbhelper/screens/HomeScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/HomeScreen.kt +2 -2
@@ 8,7 8,7 @@ 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.BottomNavItem
import com.github.nacabaro.vbhelper.navigation.NavigationItems

@Composable
fun HomeScreen(


@@ 19,7 19,7 @@ fun HomeScreen(
            TopBanner(
                text = "VB Helper",
                onGearClick = {
                    navController.navigate(BottomNavItem.Settings.route)
                    navController.navigate(NavigationItems.Settings.route)
                }
            )
        }

M app/src/main/java/com/github/nacabaro/vbhelper/screens/StorageScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/StorageScreen.kt +32 -12
@@ 1,11 1,11 @@
package com.github.nacabaro.vbhelper.screens

import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size


@@ 21,11 21,9 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
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.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
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.utils.BitmapData
import kotlinx.coroutines.launch


@Composable
fun StorageScreen() {
fun StorageScreen(
    navController: NavController
) {
    val coroutineScope = rememberCoroutineScope()
    val application = LocalContext.current.applicationContext as VBHelper
    val storageRepository = StorageRepository(application.container.db)


@@ 96,14 98,24 @@ fun StorageScreen() {
                    ),
                    modifier = Modifier
                        .padding(8.dp)
                        .size(96.dp)

                        .size(96.dp),
                    onClick = {
                        selectedCharacter = index.id
                    }
                )

                if (selectedCharacter != null) {
                    StorageDialog(
                        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
fun StorageDialog(
    characterId: Long,
    onDismissRequest: () -> Unit
    onDismissRequest: () -> Unit,
    onSendToBracelet: () -> Unit
) {
    val coroutineScope = rememberCoroutineScope()
    val application = LocalContext.current.applicationContext as VBHelper


@@ 149,10 162,17 @@ fun StorageDialog(
                            .padding(8.dp)
                    )
                }
                Button(
                    onClick = onDismissRequest
                ) {
                    Text(text = "Close")
                Row {
                    Button(
                        onClick = onSendToBracelet
                    ) {
                        Text(text = "Send to bracelet")
                    }
                    Button(
                        onClick = onDismissRequest
                    ) {
                        Text(text = "Close")
                    }
                }
            }
        }

M app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ReadingCharacter.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ReadingCharacter.kt +2 -1
@@ 15,11 15,12 @@ import com.github.nacabaro.vbhelper.components.TopBanner

@Composable
fun ReadingCharacterScreen(
    topBannerText: String,
    onClickCancel: () -> Unit,
) {
    Scaffold (
        topBar = {
            TopBanner("Reading Character")
            TopBanner(topBannerText)
        }
    ) { innerPadding ->
        Column (

M app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ScanScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ScanScreen.kt +116 -20
@@ 12,6 12,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf


@@ 25,25 26,48 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
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.proto.Secrets
import com.github.nacabaro.vbhelper.utils.characterToNfc
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext

const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER"

@Composable
fun ScanScreen(
    navController: NavController,
    characterId: Long?,
    scanScreenController: ScanScreenController,
) {
    val secrets by scanScreenController.secretsFlow.collectAsState(null)
    var readingScreen by remember { mutableStateOf(false) }
    var writingScreen 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) {
            scanScreenController.registerActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER, object: ActivityLifecycleListener {
                override fun onPause() {


@@ 60,9 84,39 @@ fun ScanScreen(
            scanScreenController.onClickRead(secrets!!) {
                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 {
            if(readingScreen) {
            if(readingScreen || writingScreen) {
                scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
                scanScreenController.cancelRead()
            }


@@ 71,33 125,68 @@ fun ScanScreen(

    if (isDoneReadingCharacter) {
        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) {
        ReadingCharacterScreen {
        ReadingCharacterScreen("Reading character") {
            readingScreen = false
            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 {
        val context = LocalContext.current
        ChooseConnectOption(
            onClickRead = {
                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 {
                    readingScreen = true // kicks off nfc adapter in DisposableEffect
            onClickRead = when {
                characterId != 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 {
                            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
private fun ChooseConnectOption(
    onClickRead: () -> Unit,
fun ChooseConnectOption(
    onClickRead: (() -> Unit)? = null,
    onClickWrite: (() -> Unit)? = null,
) {
    Scaffold(
        topBar = { TopBanner(text = "Scan a Vital Bracelet") }


@@ 111,12 200,14 @@ private fun ChooseConnectOption(
        ) {
            ScanButton(
                text = "Vital Bracelet to App",
                onClick = onClickRead,
                disabled = onClickRead == null,
                onClick = onClickRead?: {  },
            )
            Spacer(modifier = Modifier.height(16.dp))
            ScanButton(
                text = "App to Vital Bracelet",
                onClick = {}
                disabled = onClickWrite == null,
                onClick = onClickWrite?: {  },
            )
        }
    }


@@ 127,11 218,13 @@ private fun ChooseConnectOption(
fun ScanButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier
    modifier: Modifier = Modifier,
    disabled: Boolean = false,
) {
    Button(
        onClick = onClick,
        modifier = modifier
        modifier = modifier,
        enabled = !disabled,
    ) {
        Text(
            text = text,


@@ 157,7 250,10 @@ fun ScanScreenPreview() {

            }
            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() {}
        }
        },
        characterId = null
    )
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ScanScreenController.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ScanScreenController.kt +4 -0
@@ 1,5 1,6 @@
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.source.proto.Secrets
import kotlinx.coroutines.flow.Flow


@@ 7,6 8,9 @@ import kotlinx.coroutines.flow.Flow
interface ScanScreenController {
    val secretsFlow: Flow<Secrets>
    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 registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener)

M app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ScanScreenControllerImpl.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ScanScreenControllerImpl.kt +24 -0
@@ 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
    private fun showWirelessSettings() {
        Toast.makeText(context, "NFC must be enabled", Toast.LENGTH_SHORT).show()

M app/src/main/java/com/github/nacabaro/vbhelper/source/StorageRepository.kt => app/src/main/java/com/github/nacabaro/vbhelper/source/StorageRepository.kt +14 -0
@@ 1,6 1,8 @@
package com.github.nacabaro.vbhelper.source

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.dtos.CharacterDtos



@@ 14,4 16,16 @@ class StorageRepository (
    suspend fun getSingleCharacter(id: Long): UserCharacter {
        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)
    }
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/utils/CharacterToNFCCharacter.kt => app/src/main/java/com/github/nacabaro/vbhelper/utils/CharacterToNFCCharacter.kt +86 -0
@@ 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
}
\ No newline at end of file