~cytrogen/vbhelper

dce186737dcdcfc6ca8d3ae5a9d22d938e5af0b1 — Nacho 5 months ago cf272e8
Other few things
- I changed more things to flows from the database
- Cleaned up the logic coming from the scan screen
- Added a delete button to a character. CAREFUL, IT HAS NO CONFIRMATION YET!
- Fixed a few things, now scanning is more stable and will fix the second whoops thing.
- Quick patch, should improve stability when writing to the watch
19 files changed, 752 insertions(+), 235 deletions(-)

M app/src/main/java/com/github/nacabaro/vbhelper/daos/CardDao.kt
M app/src/main/java/com/github/nacabaro/vbhelper/daos/UserCharacterDao.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ChooseCharacterScreen.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ChooseConnectionScreen.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ScanScreen.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/converters/FromNfcConverter.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/converters/ToNfcConverter.kt
R app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/{ReadingCharacter => screens/ActionScreen}.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/screens/ReadCharacterScreen.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/screens/ReadingScreen.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/screens/WriteCardScreen.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/screens/WriteCharacterScreen.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/screens/WritingScreen.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageDialog.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreen.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenController.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenControllerImpl.kt
A app/src/main/java/com/github/nacabaro/vbhelper/source/ScanRepository.kt
M app/src/main/java/com/github/nacabaro/vbhelper/source/StorageRepository.kt
M app/src/main/java/com/github/nacabaro/vbhelper/daos/CardDao.kt => app/src/main/java/com/github/nacabaro/vbhelper/daos/CardDao.kt +2 -1
@@ 5,6 5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.Card
import kotlinx.coroutines.flow.Flow

@Dao
interface CardDao {


@@ 26,7 27,7 @@ interface CardDao {
        WHERE uc.id = :id
    """
    )
    suspend fun getCardByCharacterId(id: Long): Card
    fun getCardByCharacterId(id: Long): Flow<Card>

    @Query("UPDATE Card SET name = :newName WHERE id = :id")
    suspend fun renameCard(id: Int, newName: String)

M app/src/main/java/com/github/nacabaro/vbhelper/daos/UserCharacterDao.kt => app/src/main/java/com/github/nacabaro/vbhelper/daos/UserCharacterDao.kt +2 -1
@@ 13,6 13,7 @@ import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow

@Dao
interface UserCharacterDao {


@@ 76,7 77,7 @@ interface UserCharacterDao {
        LEFT JOIN Adventure a ON a.characterId = uc.id
        """
    )
    suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
    fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>>

    @Query(
        """

M app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ChooseCharacterScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ChooseCharacterScreen.kt +3 -2
@@ 24,6 24,7 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch




@@ 35,7 36,7 @@ fun ChooseCharacterScreen(
) {
    val coroutineScope = rememberCoroutineScope()
    val application = LocalContext.current.applicationContext as VBHelper
    val storageRepository = StorageRepository(application.container.db)
    val storageRepository = StorageRepository(application.container.db, )
    val characterList = remember {
        mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList())
    }


@@ 57,7 58,7 @@ fun ChooseCharacterScreen(
                    characterList.value = storageRepository.getVBCharacters()
                }
                else -> {
                    characterList.value = storageRepository.getAllCharacters()
                    characterList.value = storageRepository.getAllCharacters().first()
                }
            }
        }

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

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner

@Composable
fun ChooseConnectOption(
    onClickRead: (() -> Unit)? = null,
    onClickWrite: (() -> Unit)? = null,
    navController: NavController
) {
    Scaffold(
        topBar = {
            TopBanner(
                text = "Scan a Vital Bracelet",
                onBackClick = {
                    navController.popBackStack()
                }
            )
        }
    ) { contentPadding ->
        Column(
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier
                .fillMaxSize()
                .padding(contentPadding)
        ) {
            ScanButton(
                text = "Vital Bracelet to App",
                disabled = onClickRead == null,
                onClick = onClickRead?: {  },
            )
            Spacer(modifier = Modifier.height(16.dp))
            ScanButton(
                text = "App to Vital Bracelet",
                disabled = onClickWrite == null,
                onClick = onClickWrite?: {  },
            )
        }
    }
}


@Composable
fun ScanButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    disabled: Boolean = false,
) {
    Button(
        onClick = onClick,
        modifier = modifier,
        enabled = !disabled,
    ) {
        Text(
            text = text,
            fontSize = 16.sp,
            modifier = Modifier
                .padding(4.dp)
        )
    }
}
\ 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 +24 -208
@@ 1,38 1,24 @@
package com.github.nacabaro.vbhelper.screens.scanScreen

import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
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
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
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.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.ReadingScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.WritingScreen
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets


@@ 55,8 41,6 @@ fun ScanScreen(
    val storageRepository = StorageRepository(application.container.db)
    var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) }

    var cardsRead by remember { mutableStateOf<List<Card>?>(null) }

    val context = LocalContext.current

    LaunchedEffect(storageRepository) {


@@ 73,143 57,33 @@ fun ScanScreen(
        }
    }

    var readingScreen by remember { mutableStateOf(false) }
    var writingScreen by remember { mutableStateOf(false) }
    var cardSelectScreen by remember { mutableStateOf(false) }
    var isDoneReadingCharacter by remember { mutableStateOf(false) }
    var isDoneSendingCard by remember { mutableStateOf(false) }
    var isDoneWritingCharacter by remember { mutableStateOf(false) }

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

                    override fun onResume() {
                        scanScreenController.onClickRead(
                            secrets = secrets!!,
                            onComplete = {
                                isDoneReadingCharacter = true
                            },
                            onMultipleCards = { cards ->
                                cardsRead = cards
                                readingScreen = false
                                cardSelectScreen = true
                                isDoneReadingCharacter = true
                            }
                        )
                    }
                }
            )
            scanScreenController.onClickRead(
                secrets = secrets!!,
                onComplete = {
                    isDoneReadingCharacter = true
                },
                onMultipleCards = { cards ->
                    cardsRead = cards
                    readingScreen = false
                    cardSelectScreen = true
                    isDoneReadingCharacter = true
                }
            )
        }
        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 {
                    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 (secrets != null && nfcCharacter != null) {
            if (!isDoneSendingCard) {
                scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
                    isDoneSendingCard = true
                }
            } else if (!isDoneWritingCharacter) {
                scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
                    isDoneWritingCharacter = true
                }
            }
        }

        onDispose {
            if(writingScreen) {
                scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
                scanScreenController.cancelRead()
            }
        }
    }

    if (isDoneReadingCharacter && !cardSelectScreen) {
        readingScreen = false
        navController.navigate(NavigationItems.Home.route)
    } else if (isDoneSendingCard && isDoneWritingCharacter) {
        writingScreen = false
        navController.navigate(NavigationItems.Home.route)
        LaunchedEffect(storageRepository) {
            withContext(Dispatchers.IO) {
                storageRepository
                    .deleteCharacter(characterId!!)
            }
        }
    }
    var readingScreen by remember { mutableStateOf(false) }

    if (readingScreen) {
        ReadingCharacterScreen("Reading character") {
            readingScreen = false
            scanScreenController.cancelRead()
        }
    } else if (writingScreen) {
        if (!isDoneSendingCard) {
            ReadingCharacterScreen("Sending card") {
    if (writingScreen && nfcCharacter != null && characterId != null) {
        WritingScreen(
            scanScreenController = scanScreenController,
            nfcCharacter = nfcCharacter!!,
            characterId = characterId,
            onComplete = {
                writingScreen = false
                scanScreenController.cancelRead()
            }
        } else if (!isDoneWritingCharacter) {
            ReadingCharacterScreen("Writing character") {
                isDoneSendingCard = false
                navController.navigate(NavigationItems.Home.route)
            },
            onCancel = {
                writingScreen = false
                scanScreenController.cancelRead()
                navController.navigate(NavigationItems.Home.route)
            }
        }
    } else if (cardSelectScreen) {
        ChooseCard(
            cards = cardsRead!!,
            onCardSelected = { card ->
                cardSelectScreen = false
                scanScreenController.flushCharacter(card.id)
        )
    } else if (readingScreen) {
        ReadingScreen(
            scanScreenController = scanScreenController,
            onCancel = {
                readingScreen = false
                navController.navigate(NavigationItems.Home.route)
            },
            onComplete = {
                readingScreen = false
                navController.navigate(NavigationItems.Home.route)
            }
        )
    } else {


@@ 247,66 121,8 @@ fun ScanScreen(
    }
}

@Composable
fun ChooseConnectOption(
    onClickRead: (() -> Unit)? = null,
    onClickWrite: (() -> Unit)? = null,
    navController: NavController
) {
    Scaffold(
        topBar = {
            TopBanner(
                text = "Scan a Vital Bracelet",
                onBackClick = {
                    navController.popBackStack()
                }
            )
        }
    ) { contentPadding ->
        Column(
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier
                .fillMaxSize()
                .padding(contentPadding)
        ) {
            ScanButton(
                text = "Vital Bracelet to App",
                disabled = onClickRead == null,
                onClick = onClickRead?: {  },
            )
            Spacer(modifier = Modifier.height(16.dp))
            ScanButton(
                text = "App to Vital Bracelet",
                disabled = onClickWrite == null,
                onClick = onClickWrite?: {  },
            )
        }
    }
}


@Composable
fun ScanButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    disabled: Boolean = false,
) {
    Button(
        onClick = onClick,
        modifier = modifier,
        enabled = !disabled,
    ) {
        Text(
            text = text,
            fontSize = 16.sp,
            modifier = Modifier
                .padding(4.dp)
        )
    }
}

@Preview(showBackground = true)
@Composable
fun ScanScreenPreview() {

M 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 +3 -4
@@ 1,13 1,11 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters

import android.util.Log
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.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardProgress
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


@@ 250,10 248,11 @@ class FromNfcConverter (
    ) {
        val vitalsHistoryWatch = nfcCharacter.vitalHistory
        val vitalsHistory = vitalsHistoryWatch.map { historyElement ->
            Log.d("VitalsHistory", "${historyElement.year.toInt()} ${historyElement.month.toInt()} ${historyElement.day.toInt()}")
            val year = if (historyElement.year.toInt() !in 2021 .. 2035) 0 else historyElement.year.toInt()

            VitalsHistory(
                charId = characterId,
                year = historyElement.year.toInt(),
                year = year,
                month = historyElement.month.toInt(),
                day = historyElement.day.toInt(),
                vitalPoints = historyElement.vitalsGained.toInt()

M 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 +2 -0
@@ 14,6 14,7 @@ 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 kotlinx.coroutines.flow.first
import java.util.Date

class ToNfcConverter(


@@ 96,6 97,7 @@ class ToNfcConverter(
        val cardData = database
            .cardDao()
            .getCardByCharacterId(userCharacter.id)
            .first()

        val appReserved = Array<UShort>(3) {
            0u

R app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ReadingCharacter.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/screens/ActionScreen.kt +6 -3
@@ 1,4 1,4 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
package com.github.nacabaro.vbhelper.screens.scanScreen.screens

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column


@@ 14,13 14,16 @@ import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner

@Composable
fun ReadingCharacterScreen(
fun ActionScreen(
    topBannerText: String,
    onClickCancel: () -> Unit,
) {
    Scaffold (
        topBar = {
            TopBanner(topBannerText)
            TopBanner(
                text = topBannerText,
                onBackClick = onClickCancel
            )
        }
    ) { innerPadding ->
        Column (

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

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner

@Composable
fun ReadCharacterScreen(
    onClickCancel: () -> Unit,
    onClickConfirm: () -> Unit
) {
    Scaffold(
        topBar = {
            TopBanner(
                text = "Read character",
                onBackClick = onClickCancel
            )
        }
    ) { innerPadding ->
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
            modifier = Modifier
                .padding(innerPadding)
                .fillMaxSize()
        ) {
            Text(
                text = "Prepare your device!",
                textAlign = TextAlign.Center
            )

            Text(
                text = "Go to connect and when ready press confirm!",
                textAlign = TextAlign.Center
            )

            Spacer(
                modifier = Modifier.padding(8.dp)
            )

            Button(
                onClick = onClickConfirm,
            ) {
                Text("Confirm")
            }
        }
    }
}
\ No newline at end of file

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

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController

@Composable
fun ReadingScreen(
    scanScreenController: ScanScreenController,
    onCancel: () -> Unit,
    onComplete: () -> Unit
) {
    val secrets by scanScreenController.secretsFlow.collectAsState(null)

    var cardsRead by remember { mutableStateOf<List<Card>?>(null) }

    var readingScreen by remember { mutableStateOf(false) }
    var isDoneReadingCharacter by remember { mutableStateOf(false) }
    var cardSelectScreen by remember { mutableStateOf(false) }

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

                    override fun onResume() {
                        scanScreenController.onClickRead(
                            secrets = secrets!!,
                            onComplete = {
                                isDoneReadingCharacter = true
                            },
                            onMultipleCards = { cards ->
                                cardsRead = cards
                                readingScreen = false
                                cardSelectScreen = true
                                isDoneReadingCharacter = true
                            }
                        )
                    }
                }
            )
            scanScreenController.onClickRead(
                secrets = secrets!!,
                onComplete = {
                    isDoneReadingCharacter = true
                },
                onMultipleCards = { cards ->
                    cardsRead = cards
                    readingScreen = false
                    cardSelectScreen = true
                    isDoneReadingCharacter = true
                }
            )
        }
        onDispose {
            if(readingScreen) {
                scanScreenController.unregisterActivityLifecycleListener(
                    SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
                )
                scanScreenController.cancelRead()
            }
        }
    }

    if (isDoneReadingCharacter && !cardSelectScreen) {
        readingScreen = false
        onComplete()
    }

    if (!readingScreen) {
        ReadCharacterScreen(
            onClickConfirm = {
                readingScreen = true
            },
            onClickCancel = {
                onCancel()
            }
        )
    }

    if (readingScreen) {
        ActionScreen("Reading character") {
            readingScreen = false
            scanScreenController.cancelRead()
            onCancel()
        }
    } else if (cardSelectScreen) {
        ChooseCard(
            cards = cardsRead!!,
            onCardSelected = { card ->
                cardSelectScreen = false
                scanScreenController.flushCharacter(card.id)
            }
        )
    }
}
\ No newline at end of file

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

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap

@Composable
fun WriteCardScreen(
    characterId: Long,
    onClickCancel: () -> Unit,
    onClickConfirm: () -> Unit
) {
    val application = LocalContext.current.applicationContext as VBHelper
    val database = application.container.db
    val scanRepository = ScanRepository(database)
    val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
        id = 0,
        cardId = 0,
        name = "",
        logo = byteArrayOf(),
        logoHeight = 0,
        logoWidth = 0,
        stageCount = 0,
        isBEm = false
    ))

    Scaffold(
        topBar = {
            TopBanner(
                text = "Writing card details",
                onBackClick = onClickCancel
            )
        }
    ) { innerPadding ->
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
            modifier = Modifier
                .padding(innerPadding)
                .fillMaxSize()
        ) {
            Card (
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(8.dp)
            ) {
                Row (
                    modifier = Modifier.padding(16.dp),
                ){
                    if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
                        val charaBitmapData = BitmapData(
                            bitmap = cardDetails.logo,
                            width = cardDetails.logoWidth,
                            height = cardDetails.logoHeight
                        )
                        val charaImageBitmapData = charaBitmapData.getImageBitmap(
                            context = LocalContext.current,
                            multiplier = 4,
                            obscure = false
                        )

                        Card (
                            colors = CardColors(
                                containerColor = MaterialTheme.colorScheme.surfaceVariant,
                                contentColor = MaterialTheme.colorScheme.contentColorFor(
                                    backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
                                ),
                                disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
                                disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
                                    backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
                                )
                            )
                        ) {
                            Image(
                                bitmap = charaImageBitmapData.imageBitmap,
                                contentDescription = "Icon",
                                modifier = Modifier
                                    .size(charaImageBitmapData.dpWidth)
                                    .padding(8.dp),
                                filterQuality = FilterQuality.None
                            )
                        }
                    }

                    Spacer(
                        modifier = Modifier.width(8.dp)
                    )

                    Column {
                        Text("Get your device Ready!")
                        Text("You will need ${cardDetails.name} card!")
                    }
                }

            }

            Button(
                onClick = onClickConfirm,
            ) {
                Text("Confirm")
            }
        }
    }
}

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

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap

@Composable
fun WriteCharacterScreen(
    characterId: Long,
    onClickCancel: () -> Unit,
    onClickConfirm: () -> Unit
) {
    val application = LocalContext.current.applicationContext as VBHelper
    val database = application.container.db
    val scanRepository = ScanRepository(database)
    val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
        id = 0,
        cardId = 0,
        name = "",
        logo = byteArrayOf(),
        logoHeight = 0,
        logoWidth = 0,
        stageCount = 0,
        isBEm = false
    ))

    Scaffold(
        topBar = {
            TopBanner(
                text = "Writing character",
                onBackClick = onClickCancel
            )
        }
    ) { innerPadding ->
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
            modifier = Modifier
                .padding(innerPadding)
                .fillMaxSize()
        ) {
            Card (
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(8.dp)
            ) {
                Row (
                    modifier = Modifier.padding(16.dp),
                ){
                    if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
                        val charaBitmapData = BitmapData(
                            bitmap = cardDetails.logo,
                            width = cardDetails.logoWidth,
                            height = cardDetails.logoHeight
                        )
                        val charaImageBitmapData = charaBitmapData.getImageBitmap(
                            context = LocalContext.current,
                            multiplier = 4,
                            obscure = false
                        )

                        Card (
                            colors = CardColors(
                                containerColor = MaterialTheme.colorScheme.surfaceVariant,
                                contentColor = MaterialTheme.colorScheme.contentColorFor(
                                    backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
                                ),
                                disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
                                disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
                                    backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
                                )
                            )
                        ) {
                            Image(
                                bitmap = charaImageBitmapData.imageBitmap,
                                contentDescription = "Icon",
                                modifier = Modifier
                                    .size(charaImageBitmapData.dpWidth)
                                    .padding(8.dp),
                                filterQuality = FilterQuality.None
                            )
                        }
                    }

                    Spacer(
                        modifier = Modifier.width(8.dp)
                    )

                    Column {
                        Text("Card installed successfully!!")
                        Text("Wait until your device is ready, then tap 'Confirm'")
                    }
                }

            }

            Spacer(modifier = Modifier.height(8.dp))

            Button(
                onClick = onClickConfirm,
            ) {
                Text("Confirm")
            }
        }
    }
}
\ No newline at end of file

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

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
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

@Composable
fun WritingScreen(
    scanScreenController: ScanScreenController,
    characterId: Long,
    nfcCharacter: NfcCharacter,
    onComplete: () -> Unit,
    onCancel: () -> Unit,
) {
    val secrets by scanScreenController.secretsFlow.collectAsState(null)

    val application = LocalContext.current.applicationContext as VBHelper
    val storageRepository = StorageRepository(application.container.db)

    var writing by remember { mutableStateOf(false) }
    var writingScreen by remember { mutableStateOf(false) }
    var writingConfirmScreen by remember { mutableStateOf(false) }
    var isDoneSendingCard by remember { mutableStateOf(false) }
    var isDoneWritingCharacter by remember { mutableStateOf(false) }

    DisposableEffect(writing) {
        if (writing) {
            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 (secrets != null) {
                if (!isDoneSendingCard) {
                    scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
                        isDoneSendingCard = true
                    }
                } else if (!isDoneWritingCharacter) {
                    scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
                        isDoneWritingCharacter = true
                    }
                }
            }
        }

        onDispose {
            if (writing) {
                scanScreenController.unregisterActivityLifecycleListener(
                    SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
                )
                scanScreenController.cancelRead()
            }
        }
    }

    if (!writingScreen) {
        writing = false
        WriteCardScreen (
            characterId = characterId,
            onClickCancel = {
                scanScreenController.cancelRead()
                onCancel()
            },
            onClickConfirm = {
                writingScreen = true
            }
        )
    } else if (!isDoneSendingCard) {
        writing = true
        ActionScreen("Sending card") {
            scanScreenController.cancelRead()
            onCancel()
        }
    } else if (!writingConfirmScreen) {
        writing = false
        WriteCharacterScreen (
            characterId = characterId,
            onClickCancel = {
                scanScreenController.cancelRead()
                onCancel()
            },
            onClickConfirm = {
                writingConfirmScreen = true
            }
        )
    } else if (!isDoneWritingCharacter) {
        writing = true
        ActionScreen("Writing character") {
            isDoneSendingCard = false
            scanScreenController.cancelRead()
            onCancel()
        }
    }

    var completedWriting by remember { mutableStateOf(false) }

    LaunchedEffect(isDoneSendingCard, isDoneWritingCharacter) {
        withContext(Dispatchers.IO) {
            if (isDoneSendingCard && isDoneWritingCharacter) {
                storageRepository
                    .deleteCharacter(characterId)
                completedWriting = true
            }
        }
    }

    if (completedWriting) {
        onComplete()
    }
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageDialog.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageDialog.kt +8 -0
@@ 38,6 38,7 @@ import kotlinx.coroutines.launch
fun StorageDialog(
    characterId: Long,
    onDismissRequest: () -> Unit,
    onClickDelete: () -> Unit,
    onSendToBracelet: () -> Unit,
    onClickSetActive: () -> Unit,
    onClickSendToAdventure: (time: Long) -> Unit


@@ 144,6 145,13 @@ fun StorageDialog(
                Button(
                    modifier = Modifier
                        .fillMaxWidth(),
                    onClick = onClickDelete
                ) {
                    Text(text = "Delete character")
                }
                Button(
                    modifier = Modifier
                        .fillMaxWidth(),
                    onClick = onDismissRequest
                ) {
                    Text(text = "Close")

M app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreen.kt +16 -15
@@ 1,5 1,6 @@
package com.github.nacabaro.vbhelper.screens.storageScreen

import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable


@@ 14,11 15,10 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier


@@ 28,12 28,10 @@ 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.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.launch


@Composable


@@ 42,18 40,11 @@ fun StorageScreen(
    storageScreenController: StorageScreenControllerImpl,
    adventureScreenController: AdventureScreenControllerImpl
) {
    val coroutineScope = rememberCoroutineScope()
    val application = LocalContext.current.applicationContext as VBHelper
    val storageRepository = StorageRepository(application.container.db)
    val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) }
    var selectedCharacter by remember { mutableStateOf<Long?>(null) }
    val characterList by storageRepository.getAllCharacters().collectAsState(initial = emptyList())

    LaunchedEffect(storageRepository, selectedCharacter) {
        coroutineScope.launch {
            val characterList = storageRepository.getAllCharacters()
            monList.value = characterList
        }
    }
    var selectedCharacter by remember { mutableStateOf<Long?>(null) }

    Scaffold (
        topBar = {


@@ 65,7 56,7 @@ fun StorageScreen(
            )
        }
    ) { contentPadding ->
        if (monList.value.isEmpty()) {
        if (characterList.isEmpty()) {
            Column (
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center,


@@ 86,7 77,7 @@ fun StorageScreen(
                    .scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
                    .padding(top = contentPadding.calculateTopPadding())
            ) {
                items(monList.value) { index ->
                items(characterList) { index ->
                    CharacterEntry(
                        icon = BitmapData(
                            bitmap = index.spriteIdle,


@@ 138,6 129,16 @@ fun StorageScreen(
                            timeInMinutes = time
                        )
                    selectedCharacter = null
                },
                onClickDelete = {
                    storageScreenController
                        .deleteCharacter(
                            characterId = selectedCharacter!!,
                            onCompletion = {
                                Log.d("StorageScreen", "Character deleted")
                            }
                        )
                    selectedCharacter = null
                }
            )
        }

M app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenController.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenController.kt +1 -0
@@ 2,4 2,5 @@ package com.github.nacabaro.vbhelper.screens.storageScreen

interface StorageScreenController {
    fun setActive(characterId: Long, onCompletion: () -> Unit)
    fun deleteCharacter(characterId: Long, onCompletion: () -> Unit)
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenControllerImpl.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenControllerImpl.kt +17 -0
@@ 28,4 28,21 @@ class StorageScreenControllerImpl(
            }
        }
    }

    override fun deleteCharacter(characterId: Long, onCompletion: () -> Unit) {
        componentActivity.lifecycleScope.launch(Dispatchers.IO) {
            database
                .userCharacterDao()
                .deleteCharacterById(characterId)

            componentActivity.runOnUiThread {
                Toast.makeText(
                    componentActivity,
                    "Character deleted!",
                    Toast.LENGTH_SHORT
                ).show()
                onCompletion()
            }
        }
    }
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/source/ScanRepository.kt => app/src/main/java/com/github/nacabaro/vbhelper/source/ScanRepository.kt +13 -0
@@ 0,0 1,13 @@
package com.github.nacabaro.vbhelper.source

import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.card.Card
import kotlinx.coroutines.flow.Flow

class ScanRepository(
    val database: AppDatabase
) {
    fun getCardDetails(characterId: Long): Flow<Card> {
        return database.cardDao().getCardByCharacterId(characterId)
    }
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/source/StorageRepository.kt => app/src/main/java/com/github/nacabaro/vbhelper/source/StorageRepository.kt +2 -1
@@ 6,11 6,12 @@ import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow

class StorageRepository (
    private val db: AppDatabase
) {
    suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites> {
    fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>> {
        return db.userCharacterDao().getAllCharacters()
    }