~cytrogen/vbhelper

f7b3b7256a61a5356dbc73d2304d20388be5053c — Nacho 1 year, 2 months ago e36a700
VB NFC compatibility
- Refactored some names (not really relevant)
- Added the ability to store special missions inside the application's database
- Refactored the conversion code into two classes inside the scan screen package
- Added the missing tables to store the necessary vb data

Also not relevant to this update
- Updated adventure progress app wide, so that instead of it being stored in a character basis, it is shared across all characters in the same dim
M app/src/main/java/com/github/nacabaro/vbhelper/daos/CharacterDao.kt => app/src/main/java/com/github/nacabaro/vbhelper/daos/CharacterDao.kt +1 -7
@@ 12,12 12,6 @@ interface CharacterDao {
    @Insert
    suspend fun insertCharacter(vararg characterData: Character)

    @Query("SELECT * FROM Character")
    suspend fun getAllCharacters(): List<Character>

    @Query("SELECT * FROM Character WHERE dimId = :dimId")
    suspend fun getCharacterByDimId(dimId: Int): List<Character>

    @Query("SELECT * FROM Character WHERE monIndex = :monIndex AND dimId = :dimId LIMIT 1")
    fun getCharacterByMonIndex(monIndex: Int, dimId: Long): Character



@@ 30,7 24,7 @@ interface CharacterDao {
    @Query(
        """
        SELECT 
            d.dimId as cardId,
            d.cardId as cardId,
            c.monIndex as charId
        FROM Character c
        JOIN UserCharacter uc ON c.id = uc.charId

M app/src/main/java/com/github/nacabaro/vbhelper/daos/DiMDao.kt => app/src/main/java/com/github/nacabaro/vbhelper/daos/DiMDao.kt +17 -4
@@ 11,9 11,22 @@ interface DiMDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insertNewDim(card: Card): Long

    @Query("SELECT * FROM Card")
    suspend fun getAllDims(): List<Card>

    @Query("SELECT * FROM Card WHERE dimId = :id")
    @Query("SELECT * FROM Card WHERE cardId = :id")
    fun getDimById(id: Int): Card?

    @Query(
        """
        UPDATE Card
        SET currentStage = :currentStage
        WHERE cardId = :id
    """
    )
    fun updateCurrentStage(id: Int, currentStage: Int)

    @Query("""
        SELECT currentStage 
        FROM Card
        WHERE cardId = :id
    """)
    fun getCurrentStage(id: Int): Int
}
\ 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 +15 -1
@@ 7,7 7,9 @@ import androidx.room.Query
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos

@Dao


@@ 18,6 20,9 @@ interface UserCharacterDao {
    @Insert
    fun insertBECharacterData(characterData: BECharacterData)

    @Insert
    fun insertVBCharacterData(characterData: VBCharacterData)

    @Upsert
    fun updateCharacter(character: UserCharacter)



@@ 27,6 32,9 @@ interface UserCharacterDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertTransformationHistory(vararg transformationHistory: TransformationHistory)

    @Insert
    fun insertSpecialMissions(vararg specialMissions: SpecialMissions)

    @Query("""
        SELECT 
            c.id AS id,


@@ 39,7 47,7 @@ interface UserCharacterDao {
        JOIN Character c ON c.id = t.stageId
        WHERE monId = :monId
    """)
    fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?
    suspend fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?

    @Query(
        """


@@ 87,6 95,12 @@ interface UserCharacterDao {
    @Query("SELECT * FROM BECharacterData WHERE id = :id")
    suspend fun getBeData(id: Long): BECharacterData

    @Query("SELECT * FROM VBCharacterData WHERE id = :id")
    suspend fun getVbData(id: Long): VBCharacterData

    @Query("SELECT * FROM SpecialMissions WHERE characterId = :id")
    suspend fun getSpecialMissions(id: Long): List<SpecialMissions>

    @Query(
        """
        SELECT

M app/src/main/java/com/github/nacabaro/vbhelper/database/AppDatabase.kt => app/src/main/java/com/github/nacabaro/vbhelper/database/AppDatabase.kt +4 -0
@@ 14,8 14,10 @@ import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.characters.Adventure
import com.github.nacabaro.vbhelper.domain.characters.Dex
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
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.VBCharacterData
import com.github.nacabaro.vbhelper.domain.items.Items

@Database(


@@ 26,6 28,8 @@ import com.github.nacabaro.vbhelper.domain.items.Items
        Sprites::class,
        UserCharacter::class,
        BECharacterData::class,
        VBCharacterData::class,
        SpecialMissions::class,
        TransformationHistory::class,
        Dex::class,
        Items::class,

M app/src/main/java/com/github/nacabaro/vbhelper/domain/characters/Card.kt => app/src/main/java/com/github/nacabaro/vbhelper/domain/characters/Card.kt +2 -1
@@ 7,11 7,12 @@ import androidx.room.PrimaryKey
data class Card(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val dimId: Int,
    val cardId: Int,
    val logo: ByteArray,
    val logoWidth: Int,
    val logoHeight: Int,
    val name: String,
    val stageCount: Int,
    val currentStage: Int,
    val isBEm: Boolean
)

A app/src/main/java/com/github/nacabaro/vbhelper/domain/device_data/SpecialMissions.kt => app/src/main/java/com/github/nacabaro/vbhelper/domain/device_data/SpecialMissions.kt +20 -0
@@ 0,0 1,20 @@
package com.github.nacabaro.vbhelper.domain.device_data

import androidx.room.Entity
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.vb.SpecialMission

@Entity(

)
data class SpecialMissions (
    @PrimaryKey(autoGenerate = true) var id: Long = 0,
    var characterId: Long,
    var goal: Int,
    val watchId: Int,
    val progress: Int,
    val status: SpecialMission.Status,
    val timeElapsedInMinutes: Int,
    val timeLimitInMinutes: Int,
    val missionType: SpecialMission.Type
)
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/domain/device_data/UserCharacter.kt => app/src/main/java/com/github/nacabaro/vbhelper/domain/device_data/UserCharacter.kt +0 -1
@@ 23,7 23,6 @@ data class UserCharacter (
    var stage: Int,
    var attribute: NfcCharacter.Attribute,
    var ageInDays: Int,
    var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
    var mood: Int,
    var vitalPoints: Int,
    var transformationCountdown: Int,

M app/src/main/java/com/github/nacabaro/vbhelper/domain/device_data/VBCharacterData.kt => app/src/main/java/com/github/nacabaro/vbhelper/domain/device_data/VBCharacterData.kt +19 -2
@@ 1,4 1,21 @@
package com.github.nacabaro.vbhelper.domain.device_data

class VBCharacterData {
}
\ No newline at end of file
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = UserCharacter::class,
            parentColumns = ["id"],
            childColumns = ["id"],
            onDelete = ForeignKey.CASCADE
        )
    ]
)
data class VBCharacterData (
    @PrimaryKey val id: Long,
    val generation: Int,
    val totalTrophies: Int
)
\ No newline at end of file

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 +38 -22
@@ 34,7 34,6 @@ 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


@@ 62,29 61,43 @@ fun ScanScreen(
    LaunchedEffect(storageRepository) {
        withContext(Dispatchers.IO) {
            if(characterId != null && nfcCharacter == null) {
                nfcCharacter = characterToNfc(context, characterId)
                nfcCharacter = scanScreenController.characterToNfc(characterId)
            }
        }
    }

    DisposableEffect(readingScreen || writingScreen, isDoneSendingCard) {
    DisposableEffect(readingScreen) {
        if(readingScreen) {
            scanScreenController.registerActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER, object: ActivityLifecycleListener {
                override fun onPause() {
                    scanScreenController.cancelRead()
                }
            scanScreenController.registerActivityLifecycleListener(
                SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
                object: ActivityLifecycleListener {
                    override fun onPause() {
                        scanScreenController.cancelRead()
                    }

                override fun onResume() {
                    scanScreenController.onClickRead(secrets!!) {
                        isDoneReadingCharacter = true
                    override fun onResume() {
                        scanScreenController.onClickRead(secrets!!) {
                            isDoneReadingCharacter = true
                        }
                    }
                }

            })
            )
            scanScreenController.onClickRead(secrets!!) {
                isDoneReadingCharacter = true
            }
        } else if (writingScreen) {
        }
        onDispose {
            if(readingScreen) {
                scanScreenController.unregisterActivityLifecycleListener(
                    SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
                )
                scanScreenController.cancelRead()
            }
        }
    }

    DisposableEffect(writingScreen, isDoneSendingCard) {
        if (writingScreen) {
            scanScreenController.registerActivityLifecycleListener(
                SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
                object : ActivityLifecycleListener {


@@ 105,18 118,20 @@ fun ScanScreen(
                    }
                }
            )
            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 || writingScreen) {
            if(writingScreen) {
                scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
                scanScreenController.cancelRead()
            }


@@ 259,6 274,7 @@ fun ScanScreenPreview() {
            override fun onClickCheckCard(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
            override fun onClickWrite(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
            override fun cancelRead() {}
            override suspend fun characterToNfc(characterId: Long): NfcCharacter? { return null }
        },
        characterId = null
    )

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 +2 -0
@@ 15,4 15,6 @@ interface ScanScreenController {

    fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener)
    fun unregisterActivityLifecycleListener(key: String)

    suspend fun characterToNfc(characterId: Long): NfcCharacter?
}
\ No newline at end of file

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 +13 -101
@@ 11,22 11,19 @@ import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vbnfc.TagCommunicator
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.source.getCryptographicTransformerMap
import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets
import com.github.nacabaro.vbhelper.utils.DeviceType
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.FromNfcConverter
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.ToNfcConverter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.util.GregorianCalendar

class ScanScreenControllerImpl(
    override val secretsFlow: Flow<Secrets>,


@@ 37,6 34,8 @@ class ScanScreenControllerImpl(

    private val nfcAdapter: NfcAdapter

    private val storageRepository: AppDatabase

    init {
        val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity)
        if (maybeNfcAdapter == null) {


@@ 44,6 43,8 @@ class ScanScreenControllerImpl(
        }
        nfcAdapter = maybeNfcAdapter
        checkSecrets()
        val application = componentActivity.applicationContext as VBHelper
        storageRepository = application.container.db
    }

    override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {


@@ 152,102 153,13 @@ class ScanScreenControllerImpl(
    }

    private fun addCharacterScannedIntoDatabase(nfcCharacter: NfcCharacter): String {
        val application = componentActivity.applicationContext as VBHelper
        val storageRepository = application.container.db

        val dimData = storageRepository
            .dimDao()
            .getDimById(nfcCharacter.dimId.toInt())

        if (dimData == null) return "Card not found"

        val cardCharData = storageRepository
            .characterDao()
            .getCharacterByMonIndex(nfcCharacter.charIndex.toInt(), dimData.id)

        val characterData = UserCharacter(
            charId = cardCharData.id,
            stage = nfcCharacter.stage.toInt(),
            attribute = nfcCharacter.attribute,
            ageInDays = nfcCharacter.ageInDays.toInt(),
            nextAdventureMissionStage = nfcCharacter.nextAdventureMissionStage.toInt(),
            mood = nfcCharacter.mood.toInt(),
            vitalPoints = nfcCharacter.vitalPoints.toInt(),
            transformationCountdown = nfcCharacter.transformationCountdownInMinutes.toInt(),
            injuryStatus = nfcCharacter.injuryStatus,
            trophies = nfcCharacter.trophies.toInt(),
            currentPhaseBattlesWon = nfcCharacter.currentPhaseBattlesWon.toInt(),
            currentPhaseBattlesLost = nfcCharacter.currentPhaseBattlesLost.toInt(),
            totalBattlesWon = nfcCharacter.totalBattlesWon.toInt(),
            totalBattlesLost = nfcCharacter.totalBattlesLost.toInt(),
            activityLevel = nfcCharacter.activityLevel.toInt(),
            heartRateCurrent = nfcCharacter.heartRateCurrent.toInt(),
            characterType = when (nfcCharacter) {
                is BENfcCharacter -> DeviceType.BEDevice
                else -> DeviceType.VBDevice
            },
            isActive = true
        )

        storageRepository
            .userCharacterDao()
            .clearActiveCharacter()

        val characterId: Long = storageRepository
            .userCharacterDao()
            .insertCharacterData(characterData)

        if (nfcCharacter is BENfcCharacter) {
            val extraCharacterData = BECharacterData(
                id = characterId,
                trainingHp = nfcCharacter.trainingHp.toInt(),
                trainingAp = nfcCharacter.trainingAp.toInt(),
                trainingBp = nfcCharacter.trainingBp.toInt(),
                remainingTrainingTimeInMinutes = nfcCharacter.remainingTrainingTimeInMinutes.toInt(),
                itemEffectActivityLevelValue = nfcCharacter.itemEffectActivityLevelValue.toInt(),
                itemEffectMentalStateValue = nfcCharacter.itemEffectMentalStateValue.toInt(),
                itemEffectMentalStateMinutesRemaining = nfcCharacter.itemEffectMentalStateMinutesRemaining.toInt(),
                itemEffectActivityLevelMinutesRemaining = nfcCharacter.itemEffectActivityLevelMinutesRemaining.toInt(),
                itemEffectVitalPointsChangeValue = nfcCharacter.itemEffectVitalPointsChangeValue.toInt(),
                itemEffectVitalPointsChangeMinutesRemaining = nfcCharacter.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
                abilityRarity = nfcCharacter.abilityRarity,
                abilityType = nfcCharacter.abilityType.toInt(),
                abilityBranch = nfcCharacter.abilityBranch.toInt(),
                abilityReset = nfcCharacter.abilityReset.toInt(),
                rank = nfcCharacter.abilityReset.toInt(),
                itemType = nfcCharacter.itemType.toInt(),
                itemMultiplier = nfcCharacter.itemMultiplier.toInt(),
                itemRemainingTime = nfcCharacter.itemRemainingTime.toInt(),
                otp0 = "", //nfcCharacter.value!!.otp0.toString(),
                otp1 = "", //nfcCharacter.value!!.otp1.toString(),
                minorVersion = nfcCharacter.characterCreationFirmwareVersion.minorVersion.toInt(),
                majorVersion = nfcCharacter.characterCreationFirmwareVersion.majorVersion.toInt(),
            )

            storageRepository
                .userCharacterDao()
                .insertBECharacterData(extraCharacterData)

            val transformationHistoryWatch = nfcCharacter.transformationHistory
            transformationHistoryWatch.map { item ->
                if (item.toCharIndex.toInt() != 255) {
                    val date = GregorianCalendar(item.year.toInt(), item.month.toInt(), item.day.toInt())
                        .time
                        .time

                    storageRepository
                        .characterDao()
                        .insertTransformation(characterId, item.toCharIndex.toInt(), dimData.id, date)
        val fromNfcConverter = FromNfcConverter(componentActivity)
        return fromNfcConverter.addCharacter(nfcCharacter)
    }

                    storageRepository
                        .dexDao()
                        .insertCharacter(item.toCharIndex.toInt(), dimData.id, date)
                }
            }
        } else if (nfcCharacter is VBNfcCharacter) {
            return "Not implemented yet"
        }

        return "Done reading character!"
    override suspend fun characterToNfc(characterId: Long): NfcCharacter {
        val nfcCharacterConverter = ToNfcConverter(componentActivity)
        return nfcCharacterConverter.characterToNfc(characterId)
    }
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/converters/FromNfcConverter.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/converters/FromNfcConverter.kt +187 -0
@@ 0,0 1,187 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters

import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter
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.domain.characters.Card
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.utils.DeviceType
import java.util.GregorianCalendar

class FromNfcConverter (
    componentActivity: ComponentActivity
) {
    private val application = componentActivity.applicationContext as VBHelper
    private val database = application.container.db

    fun addCharacter(nfcCharacter: NfcCharacter): String {
        val dimData = database
            .dimDao()
            .getDimById(nfcCharacter.dimId.toInt())

        if (dimData == null)
            return "Card not found"

        val cardCharData = database
            .characterDao()
            .getCharacterByMonIndex(nfcCharacter.charIndex.toInt(), dimData.id)

        database
            .dimDao()
            .updateCurrentStage(
                id = nfcCharacter.dimId.toInt(),
                currentStage = nfcCharacter.nextAdventureMissionStage.toInt()
            )

        val characterData = UserCharacter(
            charId = cardCharData.id,
            stage = nfcCharacter.stage.toInt(),
            attribute = nfcCharacter.attribute,
            ageInDays = nfcCharacter.ageInDays.toInt(),
            mood = nfcCharacter.mood.toInt(),
            vitalPoints = nfcCharacter.vitalPoints.toInt(),
            transformationCountdown = nfcCharacter.transformationCountdownInMinutes.toInt(),
            injuryStatus = nfcCharacter.injuryStatus,
            trophies = nfcCharacter.trophies.toInt(),
            currentPhaseBattlesWon = nfcCharacter.currentPhaseBattlesWon.toInt(),
            currentPhaseBattlesLost = nfcCharacter.currentPhaseBattlesLost.toInt(),
            totalBattlesWon = nfcCharacter.totalBattlesWon.toInt(),
            totalBattlesLost = nfcCharacter.totalBattlesLost.toInt(),
            activityLevel = nfcCharacter.activityLevel.toInt(),
            heartRateCurrent = nfcCharacter.heartRateCurrent.toInt(),
            characterType = when (nfcCharacter) {
                is BENfcCharacter -> DeviceType.BEDevice
                else -> DeviceType.VBDevice
            },
            isActive = true
        )

        database
            .userCharacterDao()
            .clearActiveCharacter()

        val characterId: Long = database
            .userCharacterDao()
            .insertCharacterData(characterData)

        if (nfcCharacter is BENfcCharacter) {
            addBeCharacterToDatabase(
                characterId = characterId,
                nfcCharacter = nfcCharacter
            )
        } else if (nfcCharacter is VBNfcCharacter) {
            addVbCharacterToDatabase(
                characterId = characterId,
                nfcCharacter = nfcCharacter
            )
        }

        addTransformationHistoryToDatabase(
            characterId = characterId,
            nfcCharacter = nfcCharacter,
            dimData = dimData
        )

        return "Done reading character!"

    }

    private fun addVbCharacterToDatabase(characterId: Long, nfcCharacter: VBNfcCharacter) {
        val extraCharacterData = VBCharacterData(
            id = characterId,
            generation = nfcCharacter.generation.toInt(),
            totalTrophies = nfcCharacter.totalTrophies.toInt()
        )

        val specialMissionsWatch = nfcCharacter.specialMissions
        val specialMissionsDb = specialMissionsWatch.map { item ->
            SpecialMissions(
                characterId = characterId,
                goal = item.goal.toInt(),
                watchId = item.id.toInt(),
                progress = item.progress.toInt(),
                status = item.status,
                timeElapsedInMinutes = item.timeElapsedInMinutes.toInt(),
                timeLimitInMinutes = item.timeLimitInMinutes.toInt(),
                missionType = item.type,
            )
        }

        database
            .userCharacterDao()
            .insertVBCharacterData(extraCharacterData)

        database
            .userCharacterDao()
            .insertSpecialMissions(*specialMissionsDb.toTypedArray())
    }

    private fun addBeCharacterToDatabase(characterId: Long, nfcCharacter: BENfcCharacter) {
        val extraCharacterData = BECharacterData(
            id = characterId,
            trainingHp = nfcCharacter.trainingHp.toInt(),
            trainingAp = nfcCharacter.trainingAp.toInt(),
            trainingBp = nfcCharacter.trainingBp.toInt(),
            remainingTrainingTimeInMinutes = nfcCharacter.remainingTrainingTimeInMinutes.toInt(),
            itemEffectActivityLevelValue = nfcCharacter.itemEffectActivityLevelValue.toInt(),
            itemEffectMentalStateValue = nfcCharacter.itemEffectMentalStateValue.toInt(),
            itemEffectMentalStateMinutesRemaining = nfcCharacter.itemEffectMentalStateMinutesRemaining.toInt(),
            itemEffectActivityLevelMinutesRemaining = nfcCharacter.itemEffectActivityLevelMinutesRemaining.toInt(),
            itemEffectVitalPointsChangeValue = nfcCharacter.itemEffectVitalPointsChangeValue.toInt(),
            itemEffectVitalPointsChangeMinutesRemaining = nfcCharacter.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
            abilityRarity = nfcCharacter.abilityRarity,
            abilityType = nfcCharacter.abilityType.toInt(),
            abilityBranch = nfcCharacter.abilityBranch.toInt(),
            abilityReset = nfcCharacter.abilityReset.toInt(),
            rank = nfcCharacter.abilityReset.toInt(),
            itemType = nfcCharacter.itemType.toInt(),
            itemMultiplier = nfcCharacter.itemMultiplier.toInt(),
            itemRemainingTime = nfcCharacter.itemRemainingTime.toInt(),
            otp0 = "", //nfcCharacter.value!!.otp0.toString(),
            otp1 = "", //nfcCharacter.value!!.otp1.toString(),
            minorVersion = nfcCharacter.characterCreationFirmwareVersion.minorVersion.toInt(),
            majorVersion = nfcCharacter.characterCreationFirmwareVersion.majorVersion.toInt(),
        )

        database
            .userCharacterDao()
            .insertBECharacterData(extraCharacterData)
    }

    private fun addTransformationHistoryToDatabase(characterId: Long, nfcCharacter: NfcCharacter, dimData: Card) {
        val transformationHistoryWatch = nfcCharacter.transformationHistory
        transformationHistoryWatch.map { item ->
            if (item.toCharIndex.toInt() != 255) {
                val date = GregorianCalendar(
                    item.year.toInt(),
                    item.month.toInt(),
                    item.day.toInt()
                )
                    .time
                    .time

                database
                    .characterDao()
                    .insertTransformation(
                        characterId,
                        item.toCharIndex.toInt(),
                        dimData.id,
                        date
                    )

                database
                    .dexDao()
                    .insertCharacter(
                        item.toCharIndex.toInt(),
                        dimData.id,
                        date
                    )
            }
        }
    }
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/converters/ToNfcConverter.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/converters/ToNfcConverter.kt +218 -0
@@ 0,0 1,218 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters

import android.icu.util.Calendar
import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.be.FirmwareVersion
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.database.AppDatabase
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.utils.DeviceType
import java.util.Date

class ToNfcConverter(
    private val componentActivity: ComponentActivity
) {
    private val application: VBHelper = componentActivity.applicationContext as VBHelper
    private val database: AppDatabase = application.container.db

    suspend fun characterToNfc(characterId: Long): NfcCharacter {
        val app = componentActivity.applicationContext as VBHelper
        val database = app.container.db

        val userCharacter = database
            .userCharacterDao()
            .getCharacter(characterId)

        val characterInfo = database
            .characterDao()
            .getCharacterInfo(userCharacter.charId)

        val currentCardStage = database.dimDao().getCurrentStage(characterInfo.cardId)

        return if (userCharacter.characterType == DeviceType.BEDevice)
            nfcToBENfc(characterId, characterInfo, currentCardStage, userCharacter)
        else
            nfcToVBNfc(characterId, characterInfo, currentCardStage, userCharacter)
    }

    private suspend fun nfcToVBNfc(
        characterId: Long,
        characterInfo: CharacterDtos.DiMInfo,
        currentCardStage: Int,
        userCharacter: UserCharacter
    ): VBNfcCharacter {
        val vbData = database
            .userCharacterDao()
            .getVbData(characterId)

        val specialMissions = database
            .userCharacterDao()
            .getSpecialMissions(characterId)

        val paddedTransformationArray = generateTransformationHistory(characterId)

        val watchSpecialMissions = specialMissions.map {
            SpecialMission(
                goal = it.goal.toUShort(),
                id = it.watchId.toUShort(),
                progress = it.progress.toUShort(),
                status = it.status,
                timeElapsedInMinutes = it.timeElapsedInMinutes.toUShort(),
                timeLimitInMinutes = it.timeLimitInMinutes.toUShort(),
                type = it.missionType
            )
        }

        val nfcData = VBNfcCharacter(
            dimId = characterInfo.cardId.toUShort(),
            charIndex = characterInfo.charId.toUShort(),
            stage = userCharacter.stage.toByte(),
            attribute = userCharacter.attribute,
            ageInDays = userCharacter.ageInDays.toByte(),
            nextAdventureMissionStage = currentCardStage.toByte(),
            mood = userCharacter.mood.toByte(),
            vitalPoints = userCharacter.vitalPoints.toUShort(),
            transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(),
            injuryStatus = userCharacter.injuryStatus,
            trophies = 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 = paddedTransformationArray,
            vitalHistory = Array(7) {
                NfcCharacter.DailyVitals(0u, 0u, 0u, 0u)
            },
            appReserved1 = ByteArray(12) {0},
            appReserved2 = Array(3) {0u},
            generation = vbData.generation.toUShort(),
            totalTrophies = vbData.totalTrophies.toUShort(),
            specialMissions = watchSpecialMissions.toTypedArray()
        )

        return nfcData
    }

    private suspend fun nfcToBENfc(
        characterId: Long,
        characterInfo: CharacterDtos.DiMInfo,
        currentCardStage: Int,
        userCharacter: UserCharacter
    ): BENfcCharacter {
        val beData = database
            .userCharacterDao()
            .getBeData(characterId)

        val paddedTransformationArray = generateTransformationHistory(characterId)

        val nfcData = BENfcCharacter(
            dimId = characterInfo.cardId.toUShort(),
            charIndex = characterInfo.charId.toUShort(),
            stage = userCharacter.stage.toByte(),
            attribute = userCharacter.attribute,
            ageInDays = userCharacter.ageInDays.toByte(),
            nextAdventureMissionStage = currentCardStage.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 = paddedTransformationArray,
            vitalHistory = Array(7) {
                NfcCharacter.DailyVitals(0u, 0u, 0u, 0u)
            },
            appReserved1 = ByteArray(12) {0},
            appReserved2 = Array(3) {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

    }

    private suspend fun generateTransformationHistory(
        characterId: Long
    ): Array<NfcCharacter.Transformation> {
        val transformationHistory = database
            .userCharacterDao()
            .getTransformationHistory(characterId)!!
            .map {
                val date = Date(it.transformationDate)
                val calendar = android.icu.util.GregorianCalendar()
                calendar.time = date

                NfcCharacter.Transformation(
                    toCharIndex = it.monIndex.toUByte(),
                    year = calendar
                        .get(Calendar.YEAR)
                        .toUShort(),
                    month = calendar
                        .get(Calendar.MONTH)
                        .toUByte(),
                    day = calendar
                        .get(Calendar.DAY_OF_MONTH)
                        .toUByte()
                )
            }.toTypedArray()

        val paddedTransformationArray = padTransformationArray(transformationHistory)

        return paddedTransformationArray
    }

    private 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
    }
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenControllerImpl.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenControllerImpl.kt +3 -2
@@ 115,10 115,11 @@ class SettingsScreenControllerImpl(
                val card = dimReader.readCard(fileReader, false)

                val cardModel = Card(
                    dimId = card.header.dimId,
                    cardId = card.header.dimId,
                    logo = card.spriteData.sprites[0].pixelData,
                    name = card.spriteData.text, // TODO Make user write card name// TODO Make user write card name
                    name = card.spriteData.text, // TODO Make user write card name
                    stageCount = card.adventureLevels.levels.size,
                    currentStage = 0,
                    logoHeight = card.spriteData.sprites[0].height,
                    logoWidth = card.spriteData.sprites[0].width,
                    isBEm = card is BemCard

D app/src/main/java/com/github/nacabaro/vbhelper/utils/CharacterToNFCCharacter.kt => app/src/main/java/com/github/nacabaro/vbhelper/utils/CharacterToNFCCharacter.kt +0 -99
@@ 1,99 0,0 @@
package com.github.nacabaro.vbhelper.utils

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.FirmwareVersion
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.source.StorageRepository
import java.util.Date

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 {
                val date = Date(it.transformationDate)
                val calendar = GregorianCalendar()
                calendar.time = date

                NfcCharacter.Transformation(
                    toCharIndex = it.monIndex.toUByte(),
                    year = calendar
                        .get(Calendar.YEAR)
                        .toUShort(),
                    month = calendar
                        .get(Calendar.MONTH)
                        .toUByte(),
                    day = calendar
                        .get(Calendar.DAY_OF_MONTH)
                        .toUByte()
                )
            }.toTypedArray()

        val paddedTransformationArray = padTransformationArray(transformationHistory)

        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 = paddedTransformationArray,
            vitalHistory = Array(7) {
                NfcCharacter.DailyVitals(0u, 0u, 0u, 0u)
            },
            appReserved1 = ByteArray(12) {0},
            appReserved2 = Array(3) {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

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