~cytrogen/vbhelper

409474b5d107423a618c0ddcb872bfdd98ff7e8e — Nacho 1 year, 2 months ago 5473188
Phew
- Added adventure screen, dialogs, components and controllers needed to rock.
- Modified DTOs so that it is possible to tell if a character is in adventure or not
- Updated layout of items database
- Fixed an issue with importing bems where the character name would be read incorrectly
- Also added a ton of checks
37 files changed, 1131 insertions(+), 176 deletions(-)

M app/src/main/assets/items.db
M app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt
M app/src/main/java/com/github/nacabaro/vbhelper/components/CharacterEntry.kt
M app/src/main/java/com/github/nacabaro/vbhelper/components/TopBanner.kt
A app/src/main/java/com/github/nacabaro/vbhelper/daos/AdventureDao.kt
M app/src/main/java/com/github/nacabaro/vbhelper/daos/ItemDao.kt
M app/src/main/java/com/github/nacabaro/vbhelper/daos/UserCharacterDao.kt
M app/src/main/java/com/github/nacabaro/vbhelper/database/AppDatabase.kt
R app/src/main/java/com/github/nacabaro/vbhelper/domain/{items/UserItems => characters/Adventure}.kt
M app/src/main/java/com/github/nacabaro/vbhelper/domain/items/Items.kt
M app/src/main/java/com/github/nacabaro/vbhelper/dtos/CharacterDtos.kt
M app/src/main/java/com/github/nacabaro/vbhelper/dtos/ItemDtos.kt
M app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt
M app/src/main/java/com/github/nacabaro/vbhelper/navigation/NavigationItems.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureEntry.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureScreen.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureScreenController.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureScreenControllerImpl.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/CancelAdventureDialog.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/BEBEmHomeScreen.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/BEDiMHomeScreen.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/HomeScreen.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/HomeScreenController.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/HomeScreenControllerImpl.kt
R app/src/main/java/com/github/nacabaro/vbhelper/{components => screens/itemsScreen}/ItemElement.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ItemsScreenControllerImpl.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ItemsStore.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/MyItems.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ObtainedItemDialog.kt
M app/src/main/java/com/github/nacabaro/vbhelper/screens/settingsScreen/SettingsScreenControllerImpl.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageAdventureTimeDialog.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageDialog.kt
R app/src/main/java/com/github/nacabaro/vbhelper/screens/{ => storageScreen}/StorageScreen.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenController.kt
A app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenControllerImpl.kt
M app/src/main/java/com/github/nacabaro/vbhelper/source/StorageRepository.kt
A app/src/main/res/drawable/baseline_fort_24.xml
M app/src/main/assets/items.db => app/src/main/assets/items.db +0 -0
M app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt => app/src/main/java/com/github/nacabaro/vbhelper/MainActivity.kt +20 -4
@@ 9,9 9,12 @@ import androidx.compose.runtime.Composable
import com.github.nacabaro.vbhelper.navigation.AppNavigation
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme




@@ 39,6 42,9 @@ class MainActivity : ComponentActivity() {
        )
        val settingsScreenController = SettingsScreenControllerImpl(this)
        val itemsScreenController = ItemsScreenControllerImpl(this)
        val adventureScreenController = AdventureScreenControllerImpl(this)
        val storageScreenController = StorageScreenControllerImpl(this)
        val homeScreenController = HomeScreenControllerImpl(this)

        super.onCreate(savedInstanceState)



@@ 49,7 55,10 @@ class MainActivity : ComponentActivity() {
                MainApplication(
                    scanScreenController = scanScreenController,
                    settingsScreenController = settingsScreenController,
                    itemsScreenController = itemsScreenController
                    itemsScreenController = itemsScreenController,
                    adventureScreenController = adventureScreenController,
                    homeScreenController = homeScreenController,
                    storageScreenController = storageScreenController
                )
            }
        }


@@ 77,13 86,20 @@ class MainActivity : ComponentActivity() {
    private fun MainApplication(
        scanScreenController: ScanScreenControllerImpl,
        settingsScreenController: SettingsScreenControllerImpl,
        itemsScreenController: ItemsScreenControllerImpl
    ) {
        itemsScreenController: ItemsScreenControllerImpl,
        adventureScreenController: AdventureScreenControllerImpl,
        storageScreenController: StorageScreenControllerImpl,
        homeScreenController: HomeScreenControllerImpl,

        ) {
        AppNavigation(
            applicationNavigationHandlers = AppNavigationHandlers(
                settingsScreenController,
                scanScreenController,
                itemsScreenController
                itemsScreenController,
                adventureScreenController,
                storageScreenController,
                homeScreenController
            )
        )
    }

M app/src/main/java/com/github/nacabaro/vbhelper/components/CharacterEntry.kt => app/src/main/java/com/github/nacabaro/vbhelper/components/CharacterEntry.kt +5 -1
@@ 35,6 35,7 @@ fun CharacterEntry(
    icon: BitmapData,
    modifier: Modifier = Modifier,
    obscure: Boolean = false,
    disabled: Boolean = false,
    shape: Shape = MaterialTheme.shapes.medium,
    multiplier: Int = 4,
    onClick: () -> Unit = {  }


@@ 48,7 49,10 @@ fun CharacterEntry(

    Card(
        shape = shape,
        onClick = onClick,
        onClick = when (disabled) {
            true -> { {} }
            false -> onClick
        },
        modifier = modifier
            .aspectRatio(1f)
            .padding(8.dp)

M app/src/main/java/com/github/nacabaro/vbhelper/components/TopBanner.kt => app/src/main/java/com/github/nacabaro/vbhelper/components/TopBanner.kt +14 -2
@@ 23,7 23,8 @@ fun TopBanner(
    modifier: Modifier = Modifier,
    onGearClick: (() -> Unit)? = null,
    onBackClick: (() -> Unit)? = null,
    onScanClick: (() -> Unit)? = null
    onScanClick: (() -> Unit)? = null,
    onAdventureClick: (() -> Unit)? = null
) {
    Box( // Use Box to overlay elements
        modifier = modifier


@@ 49,7 50,18 @@ fun TopBanner(
                    contentDescription = "Settings"
                )
            }
        }
        } else if (onAdventureClick != null) {
            IconButton(
                onClick = onAdventureClick,
                modifier = Modifier
                    .align(Alignment.CenterEnd) // Place gear icon at the end
            ) {
                Icon(
                    painter = painterResource(R.drawable.baseline_fort_24), // Use a gear icon
                    contentDescription = "Adventure"
                )
            }
         }

        if (onScanClick != null) {
            IconButton(

A app/src/main/java/com/github/nacabaro/vbhelper/daos/AdventureDao.kt => app/src/main/java/com/github/nacabaro/vbhelper/daos/AdventureDao.kt +41 -0
@@ 0,0 1,41 @@
package com.github.nacabaro.vbhelper.daos

import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CharacterDtos


@Dao
interface AdventureDao {
    @Query("""
        INSERT INTO Adventure (characterId, finishesAdventure)
        VALUES (:characterId, strftime('%s', 'now') + :timeInSeconds)
    """)
    fun insertNewAdventure(characterId: Long, timeInSeconds: Long)

    @Query("""
        SELECT COUNT(*) FROM Adventure
    """)
    fun getAdventureCount(): Int

    @Query("""
        SELECT
            uc.*,
            c.sprite1 AS spriteIdle,
            c.spritesWidth AS spriteWidth,
            c.spritesHeight AS spriteHeight,
            d.isBEm as isBemCard,
            a.finishesAdventure AS timeLeft
        FROM UserCharacter uc
        JOIN Character c ON uc.charId = c.id
        JOIN Card d ON c.dimId = d.id
        JOIN Adventure a ON uc.id = a.characterId
    """)
    suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites>

    @Query("""
        DELETE FROM Adventure
        WHERE characterId = :characterId
    """)
    suspend fun deleteAdventure(characterId: Long)
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/daos/ItemDao.kt => app/src/main/java/com/github/nacabaro/vbhelper/daos/ItemDao.kt +31 -23
@@ 6,40 6,48 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos

@Dao
interface ItemDao {
    @Query("""
        SELECT Items.*, UserItems.quantity
    @Query(
        """
        SELECT *
        FROM Items
        LEFT JOIN UserItems ON Items.id = UserItems.itemId
        ORDER BY Items.itemIcon ASC
    """)
    """
    )
    suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities>

    @Query("""
        SELECT Items.*, UserItems.quantity
    @Query(
        """
        SELECT *
        FROM Items
        JOIN UserItems ON Items.id = UserItems.itemId
    """)
        WHERE quantity > 0
    """
    )
    suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>

    @Query("""
        SELECT Items.*, UserItems.quantity
    @Query(
        """
        SELECT *
        FROM Items
        JOIN UserItems ON Items.id = UserItems.itemId
        WHERE UserItems.itemId = :itemId
    """)
    fun getUserItem(itemId: Long): ItemDtos.ItemsWithQuantities
        WHERE Items.id = :itemId
    """
    )
    fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities

    @Query("""
        UPDATE UserItems
    @Query(
        """
        UPDATE Items
        SET quantity = quantity - 1
        WHERE itemId = :itemId
    """)
        WHERE id = :itemId
    """
    )
    fun useItem(itemId: Long)

    @Query("""
        UPDATE UserItems
        SET quantity = quantity - :itemAmount
        WHERE itemId = :itemId
    """)
    @Query(
        """
        UPDATE Items
        SET quantity = quantity + :itemAmount
        WHERE id = :itemId
    """
    )
    suspend fun purchaseItem(itemId: Long, itemAmount: 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 +33 -4
@@ 48,14 48,39 @@ interface UserCharacterDao {
            c.sprite1 AS spriteIdle,
            c.spritesWidth AS spriteWidth,
            c.spritesHeight AS spriteHeight,
            d.isBEm as isBemCard
            c.name as nameSprite,
            c.nameWidth as nameSpriteWidth,
            c.nameHeight as nameSpriteHeight,
            d.isBEm as isBemCard,
            a.characterId = uc.id as isInAdventure
        FROM UserCharacter uc
        JOIN Character c ON uc.charId = c.id
        JOIN Card d ON c.dimId = d.id
        LEFT JOIN Adventure a ON a.characterId = uc.id
        """
    )
    suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>

    @Query(
        """
        SELECT
            uc.*,
            c.sprite1 AS spriteIdle,
            c.spritesWidth AS spriteWidth,
            c.spritesHeight AS spriteHeight,
            c.name as nameSprite,
            c.nameWidth as nameSpriteWidth,
            c.nameHeight as nameSpriteHeight,
            d.isBEm as isBemCard,
            a.characterId = uc.id as isInAdventure
        FROM UserCharacter uc
        JOIN Character c ON uc.charId = c.id
        JOIN Card d ON c.dimId = d.id
        LEFT JOIN Adventure a ON a.characterId = uc.id
        WHERE uc.id = :id
    """)
    suspend fun getCharacterWithSprites(id: Long): CharacterDtos.CharacterWithSprites

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



@@ 69,14 94,18 @@ interface UserCharacterDao {
            c.sprite1 AS spriteIdle,
            c.spritesWidth AS spriteWidth,
            c.spritesHeight AS spriteHeight,
            d.isBEm as isBemCard
            c.name as nameSprite,
            c.nameWidth as nameSpriteWidth,
            c.nameHeight as nameSpriteHeight,
            d.isBEm as isBemCard,
            a.characterId as isInAdventure            
        FROM UserCharacter uc
        JOIN Character c ON uc.charId = c.id
        JOIN Card d ON c.dimId = d.id
        LEFT JOIN Adventure a ON a.characterId = uc.id
        WHERE uc.isActive = 1
        LIMIT 1
    """
    )
    """)
    suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites?

    @Query("DELETE FROM UserCharacter WHERE id = :id")

M app/src/main/java/com/github/nacabaro/vbhelper/database/AppDatabase.kt => app/src/main/java/com/github/nacabaro/vbhelper/database/AppDatabase.kt +4 -2
@@ 2,6 2,7 @@ package com.github.nacabaro.vbhelper.database

import androidx.room.Database
import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.daos.AdventureDao
import com.github.nacabaro.vbhelper.daos.CharacterDao
import com.github.nacabaro.vbhelper.daos.DexDao
import com.github.nacabaro.vbhelper.daos.DiMDao


@@ 10,12 11,12 @@ import com.github.nacabaro.vbhelper.daos.UserCharacterDao
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.characters.Card
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.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.items.Items
import com.github.nacabaro.vbhelper.domain.items.UserItems

@Database(
    version = 1,


@@ 28,7 29,7 @@ import com.github.nacabaro.vbhelper.domain.items.UserItems
        TransformationHistory::class,
        Dex::class,
        Items::class,
        UserItems::class
        Adventure::class
    ]
)
abstract class AppDatabase : RoomDatabase() {


@@ 37,4 38,5 @@ abstract class AppDatabase : RoomDatabase() {
    abstract fun userCharacterDao(): UserCharacterDao
    abstract fun dexDao(): DexDao
    abstract fun itemDao(): ItemDao
    abstract fun adventureDao(): AdventureDao
}
\ No newline at end of file

R app/src/main/java/com/github/nacabaro/vbhelper/domain/items/UserItems.kt => app/src/main/java/com/github/nacabaro/vbhelper/domain/characters/Adventure.kt +7 -6
@@ 1,20 1,21 @@
package com.github.nacabaro.vbhelper.domain.items
package com.github.nacabaro.vbhelper.domain.characters

import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = Items::class,
            entity = UserCharacter::class,
            parentColumns = ["id"],
            childColumns = ["itemId"],
            childColumns = ["characterId"],
            onDelete = ForeignKey.CASCADE
        )
    ]
)
data class UserItems(
    @PrimaryKey val itemId: Long,
    val quantity: Int,
data class Adventure(
    @PrimaryKey val characterId: Long,
    val finishesAdventure: Long
)

M app/src/main/java/com/github/nacabaro/vbhelper/domain/items/Items.kt => app/src/main/java/com/github/nacabaro/vbhelper/domain/items/Items.kt +2 -1
@@ 10,5 10,6 @@ data class Items(
    val description: String,
    val itemIcon: Int,
    val itemLength: Int,
    val price: Int
    val price: Int,
    val quantity: Int
)

M app/src/main/java/com/github/nacabaro/vbhelper/dtos/CharacterDtos.kt => app/src/main/java/com/github/nacabaro/vbhelper/dtos/CharacterDtos.kt +31 -1
@@ 27,7 27,11 @@ object CharacterDtos {
        val spriteIdle: ByteArray,
        val spriteWidth: Int,
        val spriteHeight: Int,
        val isBemCard: Boolean
        val nameSprite: ByteArray,
        val nameSpriteWidth: Int,
        val nameSpriteHeight: Int,
        val isBemCard: Boolean,
        val isInAdventure: Boolean
    )

    data class DiMInfo(


@@ 51,4 55,30 @@ object CharacterDtos {
        val spriteHeight: Int,
        val discoveredOn: Long?
    )

    data class AdventureCharacterWithSprites(
        var id: Long = 0,
        var charId: Long,
        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,
        var injuryStatus: NfcCharacter.InjuryStatus,
        var trophies: Int,
        var currentPhaseBattlesWon: Int,
        var currentPhaseBattlesLost: Int,
        var totalBattlesWon: Int,
        var totalBattlesLost: Int,
        var activityLevel: Int,
        var heartRateCurrent: Int,
        var characterType: DeviceType,
        val spriteIdle: ByteArray,
        val spriteWidth: Int,
        val spriteHeight: Int,
        val isBemCard: Boolean,
        val timeLeft: Long
    )
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/dtos/ItemDtos.kt => app/src/main/java/com/github/nacabaro/vbhelper/dtos/ItemDtos.kt +10 -1
@@ 2,7 2,7 @@ package com.github.nacabaro.vbhelper.dtos


object ItemDtos {
    data class ItemsWithQuantities (
    data class ItemsWithQuantities(
        val id: Long,
        val name: String,
        val description: String,


@@ 11,4 11,13 @@ object ItemDtos {
        val price: Int,
        val quantity: Int,
    )

    data class PurchasedItem(
        val itemId: Long,
        val itemName: String,
        val itemDescription: String,
        val itemIcon: Int,
        val itemLength: Int,
        val itemAmount: Int
    )
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt => app/src/main/java/com/github/nacabaro/vbhelper/navigation/AppNavigation.kt +21 -4
@@ 16,15 16,22 @@ import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreen
import com.github.nacabaro.vbhelper.screens.SpriteViewer
import com.github.nacabaro.vbhelper.screens.StorageScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ChooseCharacterScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreen
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl

data class AppNavigationHandlers(
    val settingsScreenController: SettingsScreenControllerImpl,
    val scanScreenController: ScanScreenControllerImpl,
    val itemsScreenController: ItemsScreenControllerImpl
    val itemsScreenController: ItemsScreenControllerImpl,
    val adventureScreenController: AdventureScreenControllerImpl,
    val storageScreenController: StorageScreenControllerImpl,
    val homeScreenController: HomeScreenControllerImpl
)

@Composable


@@ 49,12 56,15 @@ fun AppNavigation(
            }
            composable(NavigationItems.Home.route) {
                HomeScreen(
                    navController = navController
                    navController = navController,
                    homeScreenController = applicationNavigationHandlers.homeScreenController
                )
            }
            composable(NavigationItems.Storage.route) {
                StorageScreen(
                    navController = navController
                    navController = navController,
                    adventureScreenController = applicationNavigationHandlers.adventureScreenController,
                    storageScreenController = applicationNavigationHandlers.storageScreenController
                )
            }
            composable(NavigationItems.Scan.route) {


@@ 108,6 118,13 @@ fun AppNavigation(
                    )
                }
            }
            composable(NavigationItems.Adventure.route) {
                AdventureScreen(
                    navController = navController,
                    storageScreenController = applicationNavigationHandlers
                        .adventureScreenController
                )
            }
        }
    }
}

M app/src/main/java/com/github/nacabaro/vbhelper/navigation/NavigationItems.kt => app/src/main/java/com/github/nacabaro/vbhelper/navigation/NavigationItems.kt +1 -0
@@ 19,4 19,5 @@ sealed class NavigationItems (
    object MyItems : NavigationItems("MyItems", R.drawable.baseline_data_24, "My items")
    object ItemsStore : NavigationItems("ItemsStore", R.drawable.baseline_data_24, "Items store")
    object ApplyItem : NavigationItems("ApplyItem/{itemId}", R.drawable.baseline_data_24, "Apply item")
    object Adventure : NavigationItems("Adventure", R.drawable.baseline_fort_24, "Adventure")
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureEntry.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureEntry.kt +68 -0
@@ 0,0 1,68 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
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.material3.Text
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
import java.util.Locale

@Composable
fun AdventureEntry(
    icon: BitmapData,
    timeLeft: Long,
    modifier: Modifier = Modifier,
    onClick: () -> Unit
) {
    val bitmap = remember (icon.bitmap) { icon.getBitmap() }
    val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
    val density: Float = LocalContext.current.resources.displayMetrics.density
    val dpSize = (icon.width * 4 / density).dp

    Card(
        onClick = onClick,
        modifier = modifier
            .padding(8.dp)
            .fillMaxWidth()
    ) {
        Row(
            modifier = Modifier
                .padding(8.dp)
                .height(96.dp)
        ) {
            Image(
                bitmap = imageBitmap,
                contentDescription = null,
                filterQuality = FilterQuality.None,
                modifier = Modifier
                    .size(dpSize)
            )
            Text(
                text = when {
                    timeLeft < 0 -> "Adventure finished"
                    else -> "Time left: ${formatSeconds(timeLeft)}"
                }
            )
        }
    }
}

fun formatSeconds(totalSeconds: Long): String {
    val hours = totalSeconds / 3600
    val minutes = (totalSeconds % 3600) / 60
    val seconds = totalSeconds % 60

    return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds)
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureScreen.kt +128 -0
@@ 0,0 1,128 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen

import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
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.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.Instant

@Composable
fun AdventureScreen(
    navController: NavController,
    storageScreenController: AdventureScreenControllerImpl
) {
    val coroutineScope = rememberCoroutineScope()
    val application = LocalContext.current.applicationContext as VBHelper
    val database = application.container.db
    val storageRepository = StorageRepository(database)
    val characterList = remember {
        mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
    }
    var obtainedItem by remember {
        mutableStateOf<ItemDtos.PurchasedItem?>(null)
    }

    val currentTime by produceState(initialValue = Instant.now().epochSecond) {
        while (true) {
            value = Instant.now().epochSecond
            delay(1000)
        }
    }

    var cancelAdventureDialog by remember {
        mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
    }

    LaunchedEffect(storageRepository) {
        coroutineScope.launch {
            characterList.value = storageRepository
                .getAdventureCharacters()
        }
    }

    Scaffold(
        topBar = {
            TopBanner(
                text = "Adventure",
                onBackClick = {
                    navController.popBackStack()
                }
            )
        }
    ) { contentPadding ->
        LazyColumn(
            modifier = Modifier
                .padding(top = contentPadding.calculateTopPadding())
        ) {
            items(characterList.value) {
                AdventureEntry(
                    icon = BitmapData(
                        bitmap = it.spriteIdle,
                        width = it.spriteWidth,
                        height = it.spriteHeight
                    ),
                    timeLeft = it.timeLeft - currentTime,
                    onClick = {
                        if (it.timeLeft < currentTime) {
                            storageScreenController
                                .getItemFromAdventure(it.id) { adventureResult ->
                                    obtainedItem = adventureResult
                                }
                        } else {
                            cancelAdventureDialog = it
                        }
                    }
                )
            }
        }
    }

    if (obtainedItem != null) {
        ObtainedItemDialog(
            obtainedItem = obtainedItem!!,
            onClickDismiss = {
                obtainedItem = null
            }
        )
    }

    if (cancelAdventureDialog != null) {
        CancelAdventureDialog(
            characterSprite = BitmapData(
                bitmap = cancelAdventureDialog!!.spriteIdle,
                width = cancelAdventureDialog!!.spriteWidth,
                height = cancelAdventureDialog!!.spriteHeight
            ),
            onDismissRequest = {
                cancelAdventureDialog = null
            },
            onClickConfirm = {
                storageScreenController.cancelAdventure(cancelAdventureDialog!!.id) {
                    navController.navigate(NavigationItems.Storage.route)
                }
                cancelAdventureDialog = null
            }
        )
    }
}

A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureScreenController.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureScreenController.kt +9 -0
@@ 0,0 1,9 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen

import com.github.nacabaro.vbhelper.dtos.ItemDtos

interface AdventureScreenController {
    fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
    fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit)
    fun cancelAdventure(characterId: Long, onResult: () -> Unit)
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureScreenControllerImpl.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/AdventureScreenControllerImpl.kt +100 -0
@@ 0,0 1,100 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen

import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
import kotlin.random.Random

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

    override fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long) {
        val timeInSeconds = timeInMinutes * 60
        componentActivity.lifecycleScope.launch(Dispatchers.IO) {
            val characterData = database
                .userCharacterDao()
                .getCharacter(characterId)

            if (characterData.isActive) {
                database
                    .userCharacterDao()
                    .clearActiveCharacter()
            }

            database
                .adventureDao()
                .insertNewAdventure(characterId, timeInSeconds)
        }
    }

    override fun getItemFromAdventure(
        characterId: Long,
        onResult: (ItemDtos.PurchasedItem) -> Unit
    ) {
        componentActivity.lifecycleScope.launch(Dispatchers.IO) {
            database
                .adventureDao()
                .deleteAdventure(characterId)

            val generatedItem = generateItem(characterId)

            onResult(generatedItem)
        }
    }

    override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
        componentActivity.lifecycleScope.launch(Dispatchers.IO) {
            database
                .adventureDao()
                .deleteAdventure(characterId)

            componentActivity
                .runOnUiThread {
                    Toast.makeText(
                        componentActivity,
                        "Adventure canceled",
                        Toast.LENGTH_SHORT
                    ).show()
                    onResult()
                }

        }
    }

    private suspend fun generateItem(characterId: Long): ItemDtos.PurchasedItem {
        val character = database
            .userCharacterDao()
            .getCharacter(characterId)

        val randomItem = database
            .itemDao()
            .getAllItems()
            .random()

        val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()

        database
            .itemDao()
            .purchaseItem(
                itemId = randomItem.id,
                itemAmount = random
            )

        return ItemDtos.PurchasedItem(
            itemId = randomItem.id,
            itemAmount = random,
            itemName = randomItem.name,
            itemIcon = randomItem.itemIcon,
            itemLength = randomItem.itemLength,
            itemDescription = randomItem.description
        )
    }
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/CancelAdventureDialog.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/adventureScreen/CancelAdventureDialog.kt +73 -0
@@ 0,0 1,73 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap

@Composable
fun CancelAdventureDialog(
    characterSprite: BitmapData,
    onDismissRequest: () -> Unit,
    onClickConfirm: () -> Unit
) {
    val bitmap = remember (characterSprite) { characterSprite.getBitmap() }
    val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
    val density: Float = LocalContext.current.resources.displayMetrics.density
    val dpSize = (characterSprite.width * 4 / density).dp

    Dialog(
        onDismissRequest = onDismissRequest
    ) {
        Card {
            Column(
                modifier = Modifier
                    .padding(16.dp)
            ) {
                Row {
                    Image(
                        bitmap = imageBitmap,
                        contentDescription = null,
                        filterQuality = FilterQuality.None,
                        modifier = Modifier
                            .size(dpSize)
                    )
                    Text(
                        text = "Are you sure you want to cancel this character's adventure?"
                    )
                }
                Row(
                    modifier = Modifier
                        .padding(8.dp)
                ) {
                    Button(
                        onClick = onClickConfirm
                    ) {
                        Text(text = "Confirm")
                    }
                    Spacer(modifier = Modifier.padding(4.dp))
                    Button(
                        onClick = onDismissRequest
                    ) {
                        Text(text = "Cancel")
                    }
                }
            }
        }
    }
}
\ No newline at end of file

M app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/BEBEmHomeScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/BEBEmHomeScreen.kt +1 -1
@@ 15,7 15,7 @@ import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.ItemDisplay
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.screens.itemsScreen.getIconResource
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl

M app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/BEDiMHomeScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/BEDiMHomeScreen.kt +1 -1
@@ 16,7 16,7 @@ import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.ItemDisplay
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.screens.itemsScreen.getIconResource
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl

M app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/HomeScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/HomeScreen.kt +37 -1
@@ 4,15 4,22 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
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.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper


@@ 27,7 34,8 @@ import kotlinx.coroutines.withContext

@Composable
fun HomeScreen(
    navController: NavController
    navController: NavController,
    homeScreenController: HomeScreenControllerImpl
) {
    val application = LocalContext.current.applicationContext as VBHelper
    val storageRepository = StorageRepository(application.container.db)


@@ 35,6 43,7 @@ fun HomeScreen(
    val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
    val beData = remember { mutableStateOf<BECharacterData?>(null) }
    val vbData = remember { mutableStateOf<VBCharacterData?>(null) }
    var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }

    LaunchedEffect(storageRepository, activeMon) {
        withContext(Dispatchers.IO) {


@@ 46,6 55,13 @@ fun HomeScreen(
        }
    }

    LaunchedEffect(true) {
        homeScreenController
            .didAdventureMissionsFinish {
                adventureMissionsFinished = it
            }
    }

    Scaffold (
        topBar = {
            TopBanner(


@@ 94,6 110,26 @@ fun HomeScreen(
            }
        }
    }

    if (adventureMissionsFinished) {
        Dialog(
            onDismissRequest = { adventureMissionsFinished = false },
        ) {
            Card {
                Column(
                    modifier = Modifier
                        .padding(16.dp)
                ) {
                    Text(text = "One of your characters has finished their adventure mission!")
                    Button(onClick = {
                        adventureMissionsFinished = false
                    }) {
                        Text(text = "Dismiss")
                    }
                }
            }
        }
    }
}



A app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/HomeScreenController.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/HomeScreenController.kt +5 -0
@@ 0,0 1,5 @@
package com.github.nacabaro.vbhelper.screens.homeScreens

interface HomeScreenController {
    fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/HomeScreenControllerImpl.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/homeScreens/HomeScreenControllerImpl.kt +29 -0
@@ 0,0 1,29 @@
package com.github.nacabaro.vbhelper.screens.homeScreens

import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import kotlinx.coroutines.launch
import java.time.Instant

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

    override fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit) {
        componentActivity.lifecycleScope.launch {
            val currentTime = Instant.now().epochSecond
            val adventureCharacters = database
                .adventureDao()
                .getAdventureCharacters()

            val finishedAdventureCharacters = adventureCharacters.filter { character ->
                character.timeLeft <= currentTime
            }

            onCompletion(finishedAdventureCharacters.isNotEmpty())
        }
    }
}
\ No newline at end of file

R app/src/main/java/com/github/nacabaro/vbhelper/components/ItemElement.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ItemElement.kt +1 -2
@@ 1,4 1,4 @@
package com.github.nacabaro.vbhelper.components
package com.github.nacabaro.vbhelper.screens.itemsScreen

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box


@@ 25,7 25,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme

@Composable

M app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ItemsScreenControllerImpl.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ItemsScreenControllerImpl.kt +1 -1
@@ 107,7 107,7 @@ class ItemsScreenControllerImpl (
    private fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities {
        return database
            .itemDao()
            .getUserItem(itemId)
            .getItem(itemId)
    }

    private fun consumeItem(itemId: Long) {

M app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ItemsStore.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ItemsStore.kt +0 -4
@@ 15,10 15,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.ItemDialog
import com.github.nacabaro.vbhelper.components.ItemElement
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.components.getLengthResource
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.ItemsRepository

M app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/MyItems.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/MyItems.kt +0 -4
@@ 19,10 19,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.ItemDialog
import com.github.nacabaro.vbhelper.components.ItemElement
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.components.getLengthResource
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems

A app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ObtainedItemDialog.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/itemsScreen/ObtainedItemDialog.kt +89 -0
@@ 0,0 1,89 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.dtos.ItemDtos

@Composable
fun ObtainedItemDialog(
    obtainedItem: ItemDtos.PurchasedItem,
    onClickDismiss: () -> Unit
) {
    Dialog(
        onDismissRequest = onClickDismiss
    ) {
        Card {
            Column(
                modifier = Modifier
                    .padding(16.dp)
            ) {
                Column (
                    modifier = Modifier
                        .padding(16.dp)
                ) {
                    Row {
                        Box(modifier = Modifier) {
                            Icon(
                                painter = painterResource(id = getIconResource(obtainedItem.itemIcon)),
                                contentDescription = null,
                                modifier = Modifier
                                    .size(96.dp)
                                    .align(Alignment.Center)
                            )
                            Icon(
                                painter = painterResource(id = getLengthResource(obtainedItem.itemLength)),
                                contentDescription = null,
                                tint = MaterialTheme.colorScheme.outline,
                                modifier = Modifier
                                    .size(64.dp)
                                    .align(Alignment.BottomEnd)
                            )
                        }
                        Column (
                            modifier = Modifier
                                .padding(16.dp)
                        ) {
                            Text(
                                fontSize = MaterialTheme.typography.titleLarge.fontSize,
                                text = obtainedItem.itemName,
                                modifier = Modifier
                                    .fillMaxWidth()
                            )
                        }
                    }
                    Text(
                        textAlign = TextAlign.Center,
                        fontSize = MaterialTheme.typography.bodyMedium.fontSize,
                        fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
                        text = obtainedItem.itemDescription
                    )
                    Text(
                        textAlign = TextAlign.Center,
                        fontSize = MaterialTheme.typography.bodySmall.fontSize,
                        fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
                        text = "You have obtained ${obtainedItem.itemAmount} of this item",
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(16.dp)
                    )
                }
            }
        }
    }

}
\ 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 +1 -1
@@ 131,7 131,7 @@ class SettingsScreenControllerImpl(
                val characters = card.characterStats.characterEntries

                var spriteCounter = when (card is BemCard) {
                    true -> 55
                    true -> 54
                    false -> 10
                }


A app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageAdventureTimeDialog.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageAdventureTimeDialog.kt +107 -0
@@ 0,0 1,107 @@
package com.github.nacabaro.vbhelper.screens.storageScreen

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R

fun getAdventureTime(time: Int): String {
    return when (time) {
        360 -> "6 hours"
        720 -> "12 hours"
        1440 -> "24 hours"
        else -> "Unknown"
    }
}

@Composable
fun StorageAdventureTimeDialog(
    onClickSendToAdventure: (time: Long) -> Unit,
    onDismissRequest: () -> Unit
) {
    val times = arrayOf(360, 720, 1440)
    var expanded by remember { mutableStateOf(false) }
    var itemPosition by remember { mutableIntStateOf(-1) }

    Dialog(
        onDismissRequest = onDismissRequest
    ) {
        Card {
            Column(
                modifier = Modifier
                    .padding(16.dp)
            ) {
                Box(
                    modifier = Modifier
                        .padding(16.dp)
                ) {
                    Row (
                        horizontalArrangement = Arrangement.SpaceBetween,
                        modifier = Modifier
                            .width(256.dp)
                            .clickable(true) {
                                expanded = true
                            }
                    ) {
                        Text(
                            text = when (itemPosition) {
                                -1 -> "Choose time"
                                else -> getAdventureTime(times[itemPosition])
                            }
                        )
                        Icon(
                            painter = painterResource(R.drawable.baseline_single_arrow_down),
                            contentDescription = "Show more"
                        )
                    }
                    DropdownMenu(
                        expanded = expanded,
                        onDismissRequest = { expanded = false },
                        modifier = Modifier
                            .width(256.dp)
                    ) {
                        times.forEach { time ->
                            DropdownMenuItem(
                                text = { Text(getAdventureTime(time)) },
                                onClick = {
                                    itemPosition = times.indexOf(time)
                                    expanded = false
                                }
                            )
                        }
                    }
                }
                Button(
                    onClick = {
                        if (itemPosition != -1) {
                            onClickSendToAdventure(times[itemPosition].toLong())
                            onDismissRequest()
                        }
                    }
                ) {
                    Text(text = "Send on adventure")
                }
            }
        }
    }
}
\ No newline at end of file

A app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageDialog.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageDialog.kt +159 -0
@@ 0,0 1,159 @@
package com.github.nacabaro.vbhelper.screens.storageScreen

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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
import kotlinx.coroutines.launch

@Composable
fun StorageDialog(
    characterId: Long,
    onDismissRequest: () -> Unit,
    onSendToBracelet: () -> Unit,
    onClickSetActive: () -> Unit,
    onClickSendToAdventure: (time: Long) -> Unit
) {
    val coroutineScope = rememberCoroutineScope()
    val application = LocalContext.current.applicationContext as VBHelper
    val storageRepository = StorageRepository(application.container.db)
    val character = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
    val characterSprite = remember { mutableStateOf<BitmapData?>(null) }
    val characterName = remember { mutableStateOf<BitmapData?>(null) }
    var onSendToAdventureClicked by remember { mutableStateOf(false) }

    LaunchedEffect(storageRepository) {
        coroutineScope.launch {
            character.value = storageRepository.getSingleCharacter(characterId)
            characterSprite.value = BitmapData(
                bitmap = character.value!!.spriteIdle,
                width = character.value!!.spriteWidth,
                height = character.value!!.spriteHeight
            )
            characterName.value = BitmapData(
                bitmap = character.value!!.nameSprite,
                width = character.value!!.nameSpriteWidth,
                height = character.value!!.nameSpriteHeight
            )
        }
    }

    Dialog(
        onDismissRequest = onDismissRequest,
        properties = DialogProperties(
            dismissOnBackPress = true,
            dismissOnClickOutside = true
        )
    ) {
        Card(
            shape = RoundedCornerShape(16.dp)
        ) {
            Column (
                modifier = Modifier
                    .padding(16.dp)
            ) {
                if (character.value != null &&
                    characterSprite.value != null &&
                    characterName.value != null
                ) {
                    Row(
                        verticalAlignment = Alignment.CenterVertically,
                    ) {
                        val bitmap = remember (characterSprite.value!!) { characterSprite.value!!.getBitmap() }
                        val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
                        val density: Float = LocalContext.current.resources.displayMetrics.density
                        val dpSize = (characterSprite.value!!.width * 4 / density).dp
                        Image(
                            bitmap = imageBitmap,
                            contentDescription = "Character image",
                            filterQuality = FilterQuality.None,
                            modifier = Modifier
                                .size(dpSize)
                        )
                        val nameBitmap = remember (characterName.value!!) { characterName.value!!.getBitmap() }
                        val nameImageBitmap = remember(nameBitmap) { nameBitmap.asImageBitmap() }
                        val nameDpSize = (characterName.value!!.width * 4 / density).dp
                        Image(
                            bitmap = nameImageBitmap,
                            contentDescription = "Character image",
                            filterQuality = FilterQuality.None,
                            modifier = Modifier
                                .size(nameDpSize)
                        )
                    }
                }
                Row(
                    horizontalArrangement = Arrangement.Center,
                    modifier = Modifier
                        .fillMaxWidth()
                ) {
                    Button(
                        onClick = onSendToBracelet,
                    ) {
                        Text(text = "Send to bracelet")
                    }
                    Spacer(
                        modifier = Modifier
                            .padding(4.dp)
                    )
                    Button(
                        onClick = onClickSetActive,
                    ) {
                        Text(text = "Set active")
                    }
                }
                Button(
                    onClick = {
                        onSendToAdventureClicked = true
                    },
                ) {
                    Text(text = "Send to adventure")
                }
                Button(
                    modifier = Modifier
                        .fillMaxWidth(),
                    onClick = onDismissRequest
                ) {
                    Text(text = "Close")
                }
            }
        }
    }

    if (onSendToAdventureClicked) {
        StorageAdventureTimeDialog(
            onClickSendToAdventure = { time ->
                onClickSendToAdventure(time)
            },
            onDismissRequest = { onSendToAdventureClicked = false }
        )
    }
}
\ No newline at end of file

R app/src/main/java/com/github/nacabaro/vbhelper/screens/StorageScreen.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreen.kt +56 -105
@@ 1,20 1,16 @@
package com.github.nacabaro.vbhelper.screens
package com.github.nacabaro.vbhelper.screens.storageScreen

import android.widget.Toast
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable


@@ 28,35 24,31 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext


@Composable
fun StorageScreen(
    navController: NavController
    navController: NavController,
    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) }

    LaunchedEffect(storageRepository) {
    LaunchedEffect(storageRepository, selectedCharacter) {
        coroutineScope.launch {
            val characterList = storageRepository.getAllCharacters()
            monList.value = characterList


@@ 64,7 56,14 @@ fun StorageScreen(
    }

    Scaffold (
        topBar = { TopBanner(text = "My characters") }
        topBar = {
            TopBanner(
                text = "My characters",
                onAdventureClick = {
                    navController.navigate(NavigationItems.Adventure.route)
                }
            )
        }
    ) { contentPadding ->
        if (monList.value.isEmpty()) {
            Column (


@@ 80,25 79,36 @@ fun StorageScreen(
                    modifier = Modifier
                )
            }
        }

        LazyVerticalGrid(
            columns = GridCells.Fixed(3),
            modifier = Modifier
                .scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
                .padding(top = contentPadding.calculateTopPadding())
        ) {
            items(monList.value) { index ->
                CharacterEntry(
                    icon = BitmapData(
                        bitmap = index.spriteIdle,
                        width = index.spriteWidth,
                        height = index.spriteHeight
                    ),
                    onClick = {
                        selectedCharacter = index.id
                    }
                )
        } else {
            LazyVerticalGrid(
                columns = GridCells.Fixed(3),
                modifier = Modifier
                    .scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
                    .padding(top = contentPadding.calculateTopPadding())
            ) {
                items(monList.value) { index ->
                    CharacterEntry(
                        icon = BitmapData(
                            bitmap = index.spriteIdle,
                            width = index.spriteWidth,
                            height = index.spriteHeight
                        ),
                        onClick = {
                            if (!index.isInAdventure) {
                                selectedCharacter = index.id
                            } else {
                                Toast.makeText(
                                    application,
                                    "This character is in an adventure",
                                    Toast.LENGTH_SHORT
                                ).show()
                                navController.navigate(
                                    NavigationItems.Adventure.route
                                )
                            }
                        },
                    )
                }
            }
        }



@@ 107,13 117,11 @@ fun StorageScreen(
                characterId = selectedCharacter!!,
                onDismissRequest = { selectedCharacter = null },
                onClickSetActive = {
                    coroutineScope.launch {
                        withContext(Dispatchers.IO) {
                            storageRepository.setActiveCharacter(selectedCharacter!!)
                    storageScreenController
                        .setActive(selectedCharacter!!) {
                            selectedCharacter = null
                            navController.navigate(NavigationItems.Home.route)
                        }
                        navController.navigate(NavigationItems.Home.route)
                    }
                },
                onSendToBracelet = {
                    navController.navigate(


@@ 122,73 130,16 @@ fun StorageScreen(
                            selectedCharacter.toString()
                        )
                    )
                },
                onClickSendToAdventure = { time ->
                    adventureScreenController
                        .sendCharacterToAdventure(
                            characterId = selectedCharacter!!,
                            timeInMinutes = time
                        )
                    selectedCharacter = null
                }
            )
        }
    }
}

@Composable
fun StorageDialog(
    characterId: Long,
    onDismissRequest: () -> Unit,
    onSendToBracelet: () -> Unit,
    onClickSetActive: () -> Unit
) {
    val coroutineScope = rememberCoroutineScope()
    val application = LocalContext.current.applicationContext as VBHelper
    val storageRepository = StorageRepository(application.container.db)
    val character = remember { mutableStateOf<UserCharacter?>(null) }

    LaunchedEffect(storageRepository) {
        coroutineScope.launch {
            character.value = storageRepository.getSingleCharacter(characterId)
        }
    }

    Dialog(
        onDismissRequest = onDismissRequest,
        properties = DialogProperties(
            dismissOnBackPress = true,
            dismissOnClickOutside = true
        )
    ) {
        Card(
            shape = RoundedCornerShape(16.dp)
        ) {
            Column (
                modifier = Modifier
                    .padding(16.dp)
            ) {
                if (character.value != null) {
                    Text(
                        text = character.value?.toString() ?: "Loading...",
                        textAlign = TextAlign.Center,
                        modifier = Modifier
                            .padding(8.dp)
                    )
                }
                Row (
                    modifier = Modifier
                        .verticalScroll(rememberScrollState())
                ) {
                    Button(
                        onClick = onSendToBracelet
                    ) {
                        Text(text = "Send to bracelet")
                    }
                    Button(
                        onClick = onClickSetActive
                    ) {
                        Text(text = "Set active")
                    }
                    Button(
                        onClick = onDismissRequest
                    ) {
                        Text(text = "Close")
                    }
                }
            }
        }
    }
}
\ No newline at end of file

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

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

A app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenControllerImpl.kt => app/src/main/java/com/github/nacabaro/vbhelper/screens/storageScreen/StorageScreenControllerImpl.kt +28 -0
@@ 0,0 1,28 @@
package com.github.nacabaro.vbhelper.screens.storageScreen

import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import kotlinx.coroutines.launch

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

    override fun setActive(characterId: Long, onCompletion: () -> Unit) {
        componentActivity.lifecycleScope.launch {
            database.userCharacterDao().setActiveCharacter(characterId)
            componentActivity.runOnUiThread {
                Toast.makeText(
                    componentActivity,
                    "Active character updated!",
                    Toast.LENGTH_SHORT
                ).show()
                onCompletion()
            }
        }
    }
}
\ 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 +4 -7
@@ 2,8 2,6 @@ package com.github.nacabaro.vbhelper.source

import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos

class StorageRepository (


@@ 13,8 11,8 @@ class StorageRepository (
        return db.userCharacterDao().getAllCharacters()
    }

    suspend fun getSingleCharacter(id: Long): UserCharacter {
        return db.userCharacterDao().getCharacter(id)
    suspend fun getSingleCharacter(id: Long): CharacterDtos.CharacterWithSprites {
        return db.userCharacterDao().getCharacterWithSprites(id)
    }

    suspend fun getCharacterBeData(id: Long): BECharacterData {


@@ 37,8 35,7 @@ class StorageRepository (
        return db.userCharacterDao().deleteCharacterById(id)
    }

    fun setActiveCharacter(id: Long) {
        db.userCharacterDao().clearActiveCharacter()
        return db.userCharacterDao().setActiveCharacter(id)
    suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> {
        return db.adventureDao().getAdventureCharacters()
    }
}
\ No newline at end of file

A app/src/main/res/drawable/baseline_fort_24.xml => app/src/main/res/drawable/baseline_fort_24.xml +9 -0
@@ 0,0 1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="960"
    android:viewportHeight="960">
  <path
      android:pathData="M40,840v-160l80,-80v-240l-80,-80v-160h80v80h80v-80h80v80h80v-80h80v160l-80,80v40h240v-40l-80,-80v-160h80v80h80v-80h80v80h80v-80h80v160l-80,80v240l80,80v160L560,840v-120q0,-33 -23.5,-56.5T480,640q-33,0 -56.5,23.5T400,720v120L40,840ZM120,760h200v-40q0,-66 47,-113t113,-47q66,0 113,47t47,113v40h200v-47l-80,-80v-306l47,-47L633,280l47,47v153L280,480v-153l47,-47L153,280l47,47v306l-80,80v47ZM480,520Z"
      android:fillColor="#000000"/>
</vector>