Merge pull request #22 from nacabaro/database/items

Add items functionality
This commit is contained in:
nacabaro 2025-01-20 15:49:22 +01:00 committed by GitHub
commit 09394871e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 1134 additions and 69 deletions

Binary file not shown.

View File

@ -24,6 +24,7 @@ import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
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.ui.theme.VBHelperTheme
@ -63,12 +64,17 @@ class MainActivity : ComponentActivity() {
this::unregisterActivityLifecycleListener
)
val settingsScreenController = SettingsScreenControllerImpl(this)
val itemsScreenController = ItemsScreenControllerImpl(this)
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
VBHelperTheme {
MainApplication(scanScreenController, settingsScreenController)
MainApplication(
scanScreenController = scanScreenController,
settingsScreenController = settingsScreenController,
itemsScreenController = itemsScreenController
)
}
}
Log.i("MainActivity", "Activity onCreated")
@ -189,13 +195,15 @@ class MainActivity : ComponentActivity() {
@Composable
private fun MainApplication(
scanScreenController: ScanScreenControllerImpl,
settingsScreenController: SettingsScreenControllerImpl
settingsScreenController: SettingsScreenControllerImpl,
itemsScreenController: ItemsScreenControllerImpl
) {
AppNavigation(
applicationNavigationHandlers = AppNavigationHandlers(
settingsScreenController,
scanScreenController,
itemsScreenController
),
onClickImportCard = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {

View File

@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.components
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
@ -23,22 +22,18 @@ import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import com.github.nacabaro.vbhelper.utils.getObscuredBitmap
import java.nio.ByteBuffer
@Composable
fun CharacterEntry(
icon: BitmapData,
obscure: Boolean = false,
modifier: Modifier = Modifier,
obscure: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 3,
onClick: () -> Unit = { }
@ -79,8 +74,6 @@ fun ItemDisplay(
icon: Int,
textValue: String,
modifier: Modifier = Modifier,
iconSize: Dp = 48.dp,
textSize: TextUnit = 24.sp,
definition: String = "",
) {
val context = LocalContext.current
@ -101,15 +94,14 @@ fun ItemDisplay(
painter = painterResource(icon),
contentDescription = "Vitals",
modifier = Modifier
.fillMaxSize(0.5f)
.padding(8.dp)
.size(iconSize)
)
Text(
text = textValue,
textAlign = TextAlign.Center,
fontSize = textSize,
fontFamily = MaterialTheme.typography.titleLarge.fontFamily,
fontWeight = FontWeight.Bold
fontWeight = FontWeight.Bold,
)
}
}

View File

@ -0,0 +1,211 @@
package com.github.nacabaro.vbhelper.components
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.Spacer
import androidx.compose.foundation.layout.aspectRatio
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.material3.Button
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.tooling.preview.Preview
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
fun ItemElement(
itemIcon: Int,
lengthIcon: Int,
modifier: Modifier = Modifier,
onClick: (() -> Unit) = { }
) {
Card (
onClick = onClick,
modifier = modifier
.aspectRatio(1f)
) {
Box(modifier = Modifier.fillMaxSize()) {
// Background image (full size)
Icon(
painter = painterResource(id = itemIcon),
contentDescription = null,
modifier = Modifier
.size(96.dp)
.align(Alignment.Center)
)
Icon(
painter = painterResource(id = lengthIcon),
contentDescription = null,
tint = MaterialTheme.colorScheme.surfaceTint,
modifier = Modifier
.size(48.dp) // Set the size of the overlay image
.padding(4.dp
)
.align(Alignment.TopStart) // Align to the top end (top-right corner)
)
}
}
}
@Composable
fun ItemDialog(
name: String,
description: String,
itemIcon: Int,
lengthIcon: Int,
amount: Int,
onClickUse: () -> Unit,
onClickCancel: () -> Unit
) {
Dialog(
onDismissRequest = onClickCancel,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
Row {
Box(modifier = Modifier) {
// Background image (full size)
Icon(
painter = painterResource(id = itemIcon),
contentDescription = null,
modifier = Modifier
.size(96.dp)
.align(Alignment.Center)
)
Icon(
painter = painterResource(id = lengthIcon),
contentDescription = null,
tint = MaterialTheme.colorScheme.outline,
modifier = Modifier
.size(64.dp) // Set the size of the overlay image
.align(Alignment.BottomEnd) // Align to the top end (top-right corner)
)
}
Column (
modifier = Modifier
.padding(16.dp)
) {
Text(
fontSize = MaterialTheme.typography.titleLarge.fontSize,
text = name,
modifier = Modifier
.fillMaxWidth()
)
}
}
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
text = description
)
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have $amount of this item",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
Row (
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
) {
Button(
onClick = onClickUse
) {
Text("Use item")
}
Spacer(modifier = Modifier.size(8.dp))
Button(
onClick = onClickCancel
) {
Text("Cancel")
}
}
}
}
}
}
fun getIconResource(index: Int): Int {
return when (index) {
ItemsScreenControllerImpl.ItemTypes.PPTraining.id -> R.drawable.baseline_agility_24
ItemsScreenControllerImpl.ItemTypes.APTraining.id -> R.drawable.baseline_attack_24
ItemsScreenControllerImpl.ItemTypes.HPTraining.id -> R.drawable.baseline_shield_24
ItemsScreenControllerImpl.ItemTypes.BPTraining.id -> R.drawable.baseline_trophy_24
ItemsScreenControllerImpl.ItemTypes.AllTraining.id -> R.drawable.baseline_arrow_up_24
6 -> R.drawable.baseline_timer_24
7 -> R.drawable.baseline_rank_24
8 -> R.drawable.baseline_vitals_24
else -> R.drawable.baseline_question_mark_24
}
}
fun getLengthResource(index: Int): Int {
return when (index) {
15 -> R.drawable.baseline_15_min_timer
30 -> R.drawable.baseline_30_min_timer
60 -> R.drawable.baseline_60_min_timer
-60 -> R.drawable.baseline_60_min_timer
300 -> R.drawable.baseline_5_hour_timer
600 -> R.drawable.baseline_10_hour_timer
-720 -> R.drawable.baseline_12_hour_timer
-1440 -> R.drawable.baseline_24_hour_timer
6000 -> R.drawable.baseline_reset_24
1000 -> R.drawable.baseline_single_arrow_up
2500 -> R.drawable.baseline_double_arrow_up
5000 -> R.drawable.baseline_triple_arrow_up
9999 -> R.drawable.baseline_health_24
-500 -> R.drawable.baseline_single_arrow_down
-1000 -> R.drawable.baseline_double_arrow_down
-2500 -> R.drawable.baseline_triple_arrow_down
-9999 -> R.drawable.baseline_reset_24
else -> R.drawable.baseline_question_mark_24
}
}
@Composable
@Preview(showBackground = true)
fun PreviewItemDialog() {
VBHelperTheme {
ItemDialog(
name = "AP Training x3 (60 min)",
description = "Boosts AP during training (for 60 minutes)",
itemIcon = R.drawable.baseline_attack_24,
lengthIcon = R.drawable.baseline_60_min_timer,
onClickUse = { },
onClickCancel = { },
amount = 19
)
}
}

View File

@ -0,0 +1,45 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.ItemDtos
@Dao
interface ItemDao {
@Query("""
SELECT Items.*, UserItems.quantity
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
FROM Items
JOIN UserItems ON Items.id = UserItems.itemId
""")
suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>
@Query("""
SELECT Items.*, UserItems.quantity
FROM Items
JOIN UserItems ON Items.id = UserItems.itemId
WHERE UserItems.itemId = :itemId
""")
fun getUserItem(itemId: Long): ItemDtos.ItemsWithQuantities
@Query("""
UPDATE UserItems
SET quantity = quantity - 1
WHERE itemId = :itemId
""")
fun useItem(itemId: Long)
@Query("""
UPDATE UserItems
SET quantity = quantity - :itemAmount
WHERE itemId = :itemId
""")
suspend fun purchaseItem(itemId: Long, itemAmount: Int)
}

View File

@ -4,6 +4,7 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
@ -17,6 +18,12 @@ interface UserCharacterDao {
@Insert
fun insertBECharacterData(characterData: BECharacterData)
@Upsert
fun updateCharacter(character: UserCharacter)
@Upsert
fun updateBECharacterData(characterData: BECharacterData)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertTransformationHistory(vararg transformationHistory: TransformationHistory)

View File

@ -5,6 +5,7 @@ import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.daos.CharacterDao
import com.github.nacabaro.vbhelper.daos.DexDao
import com.github.nacabaro.vbhelper.daos.DiMDao
import com.github.nacabaro.vbhelper.daos.ItemDao
import com.github.nacabaro.vbhelper.daos.UserCharacterDao
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.characters.Card
@ -13,6 +14,8 @@ 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,
@ -23,7 +26,9 @@ import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
UserCharacter::class,
BECharacterData::class,
TransformationHistory::class,
Dex::class
Dex::class,
Items::class,
UserItems::class
]
)
abstract class AppDatabase : RoomDatabase() {
@ -31,4 +36,5 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun characterDao(): CharacterDao
abstract fun userCharacterDao(): UserCharacterDao
abstract fun dexDao(): DexDao
abstract fun itemDao(): ItemDao
}

View File

@ -22,7 +22,9 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
context = context,
klass = AppDatabase::class.java,
"internalDb"
).build()
)
.createFromAsset("items.db")
.build()
}
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)

View File

@ -20,7 +20,7 @@ data class BECharacterData (
val trainingHp: Int,
val trainingAp: Int,
val trainingBp: Int,
val remainingTrainingTimeInMinutes: Int,
var remainingTrainingTimeInMinutes: Int,
val itemEffectMentalStateValue: Int,
val itemEffectMentalStateMinutesRemaining: Int,
val itemEffectActivityLevelValue: Int,
@ -32,9 +32,9 @@ data class BECharacterData (
val abilityBranch: Int,
val abilityReset: Int,
val rank: Int,
val itemType: Int,
val itemMultiplier: Int,
val itemRemainingTime: Int,
var itemType: Int,
var itemMultiplier: Int,
var itemRemainingTime: Int,
val otp0: String,
val otp1: String,
val minorVersion: Int,

View File

@ -0,0 +1,14 @@
package com.github.nacabaro.vbhelper.domain.items
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Items(
@PrimaryKey val id: Long,
val name: String,
val description: String,
val itemIcon: Int,
val itemLength: Int,
val price: Int
)

View File

@ -0,0 +1,20 @@
package com.github.nacabaro.vbhelper.domain.items
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = Items::class,
parentColumns = ["id"],
childColumns = ["itemId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class UserItems(
@PrimaryKey val itemId: Long,
val quantity: Int,
)

View File

@ -0,0 +1,14 @@
package com.github.nacabaro.vbhelper.dtos
object ItemDtos {
data class ItemsWithQuantities (
val id: Long,
val name: String,
val description: String,
val itemIcon: Int,
val itemLength: Int,
val price: Int,
val quantity: Int,
)
}

View File

@ -11,17 +11,20 @@ import com.github.nacabaro.vbhelper.screens.BattlesScreen
import com.github.nacabaro.vbhelper.screens.DexScreen
import com.github.nacabaro.vbhelper.screens.DiMScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen
import com.github.nacabaro.vbhelper.screens.ItemsScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreen
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.itemsScreen.ChooseCharacterScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
data class AppNavigationHandlers(
val settingsScreenController: SettingsScreenControllerImpl,
val scanScreenController: ScanScreenControllerImpl,
val itemsScreenController: ItemsScreenControllerImpl
)
@Composable
@ -96,6 +99,17 @@ fun AppNavigation(
navController = navController
)
}
composable(NavigationItems.ApplyItem.route) {
val itemId = it.arguments?.getString("itemId")
if (itemId != null) {
ChooseCharacterScreen(
itemsScreenController = applicationNavigationHandlers
.itemsScreenController,
navController = navController,
itemId = itemId.toLong()
)
}
}
}
}
}

View File

@ -16,4 +16,7 @@ sealed class NavigationItems (
object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer")
object CardView : NavigationItems("Card/{cardId}", R.drawable.baseline_image_24, "Card")
object Items : NavigationItems("Items", R.drawable.baseline_data_24, "Items")
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")
}

View File

@ -1,12 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
@Composable
fun ItemsScreen(
navController: NavController
) {
Text(text = "Items")
}

View File

@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.screens
import android.util.Log
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
@ -8,7 +7,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
@ -66,7 +64,7 @@ fun StorageScreen(
}
Scaffold (
topBar = { TopBanner(text = "My Digimon") }
topBar = { TopBanner(text = "My characters") }
) { contentPadding ->
if (monList.value.isEmpty()) {
Column (
@ -101,31 +99,31 @@ fun StorageScreen(
selectedCharacter = index.id
}
)
}
}
if (selectedCharacter != null) {
StorageDialog(
characterId = selectedCharacter!!,
onDismissRequest = { selectedCharacter = null },
onClickSetActive = {
coroutineScope.launch {
withContext(Dispatchers.IO) {
storageRepository.setActiveCharacter(selectedCharacter!!)
selectedCharacter = null
}
navController.navigate(NavigationItems.Home.route)
}
},
onSendToBracelet = {
navController.navigate(
NavigationItems.Scan.route.replace(
"{characterId}",
selectedCharacter.toString()
)
)
if (selectedCharacter != null) {
StorageDialog(
characterId = selectedCharacter!!,
onDismissRequest = { selectedCharacter = null },
onClickSetActive = {
coroutineScope.launch {
withContext(Dispatchers.IO) {
storageRepository.setActiveCharacter(selectedCharacter!!)
selectedCharacter = null
}
navController.navigate(NavigationItems.Home.route)
}
},
onSendToBracelet = {
navController.navigate(
NavigationItems.Scan.route.replace(
"{characterId}",
selectedCharacter.toString()
)
)
}
}
)
}
}
}

View File

@ -15,8 +15,10 @@ 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.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.utils.BitmapData
import java.util.Locale
@ -153,6 +155,24 @@ fun BEBEmHomeScreen(
.aspectRatio(1f)
.padding(8.dp)
)
if (beData.itemRemainingTime != 0) {
ItemDisplay(
icon = getIconResource(beData.itemType),
textValue = "${beData.itemRemainingTime} m",
definition = when (beData.itemType) {
ItemsScreenControllerImpl.ItemTypes.PPTraining.id -> "PP Training"
ItemsScreenControllerImpl.ItemTypes.HPTraining.id -> "HP Training"
ItemsScreenControllerImpl.ItemTypes.APTraining.id -> "AP Training"
ItemsScreenControllerImpl.ItemTypes.BPTraining.id -> "BP Training"
ItemsScreenControllerImpl.ItemTypes.AllTraining.id -> "All Training"
else -> ""
},
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
}
}
Row (
modifier = Modifier

View File

@ -16,8 +16,10 @@ 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.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlin.text.format
@ -33,7 +35,7 @@ fun BEDiMHomeScreen(
.padding(top = contentPadding.calculateTopPadding())
.verticalScroll(state = rememberScrollState())
) {
Row (
Row(
modifier = Modifier
.fillMaxWidth()
) {
@ -49,7 +51,7 @@ fun BEDiMHomeScreen(
.weight(1f)
.aspectRatio(1f)
)
Column (
Column(
modifier = Modifier
.weight(0.5f)
.aspectRatio(0.5f)
@ -74,7 +76,7 @@ fun BEDiMHomeScreen(
)
}
}
Row (
Row(
modifier = Modifier
.fillMaxWidth()
) {
@ -108,7 +110,7 @@ fun BEDiMHomeScreen(
.padding(8.dp)
)
}
Row (
Row(
modifier = Modifier
.fillMaxWidth()
) {
@ -130,8 +132,13 @@ fun BEDiMHomeScreen(
textValue = when {
activeMon.totalBattlesLost == 0 -> "0.00 %"
else -> {
val battleWinPercentage = activeMon.totalBattlesWon.toFloat() / (activeMon.totalBattlesWon + activeMon.totalBattlesLost).toFloat()
String.format(Locale.getDefault(), "%.2f", battleWinPercentage * 100) + " %" // Specify locale
val battleWinPercentage =
activeMon.totalBattlesWon.toFloat() / (activeMon.totalBattlesWon + activeMon.totalBattlesLost).toFloat()
String.format(
Locale.getDefault(),
"%.2f",
battleWinPercentage * 100
) + " %" // Specify locale
}
},
definition = "Total battle win %",
@ -145,8 +152,13 @@ fun BEDiMHomeScreen(
textValue = when {
activeMon.totalBattlesLost == 0 -> "0.00 %"
else -> {
val battleWinPercentage = activeMon.currentPhaseBattlesWon.toFloat() / (activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost).toFloat()
String.format(Locale.getDefault(), "%.2f", battleWinPercentage * 100) + " %" // Specify locale
val battleWinPercentage =
activeMon.currentPhaseBattlesWon.toFloat() / (activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost).toFloat()
String.format(
Locale.getDefault(),
"%.2f",
battleWinPercentage * 100
) + " %" // Specify locale
}
},
definition = "Current phase win %",
@ -155,8 +167,26 @@ fun BEDiMHomeScreen(
.aspectRatio(1f)
.padding(8.dp)
)
if (beData.itemRemainingTime != 0) {
ItemDisplay(
icon = getIconResource(beData.itemType),
textValue = "${beData.itemRemainingTime} m",
definition = when (beData.itemType) {
ItemsScreenControllerImpl.ItemTypes.PPTraining.id -> "PP Training"
ItemsScreenControllerImpl.ItemTypes.HPTraining.id -> "HP Training"
ItemsScreenControllerImpl.ItemTypes.APTraining.id -> "AP Training"
ItemsScreenControllerImpl.ItemTypes.BPTraining.id -> "BP Training"
ItemsScreenControllerImpl.ItemTypes.AllTraining.id -> "All Training"
else -> ""
},
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
}
}
Row (
Row(
modifier = Modifier
.fillMaxWidth()
) {

View File

@ -0,0 +1,90 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import android.widget.Toast
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.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.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.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.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.launch
@Composable
fun ChooseCharacterScreen(
navController: NavController,
itemsScreenController: ItemsScreenControllerImpl,
itemId: Long
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val characterList = remember {
mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList())
}
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
characterList.value = storageRepository.getAllCharacters()
}
}
LaunchedEffect (selectedCharacter) {
if (selectedCharacter != null) {
itemsScreenController.applyItem(itemId, selectedCharacter!!) {
Toast.makeText(
application.applicationContext,
"Item applied!",
Toast.LENGTH_SHORT
).show()
navController.popBackStack()
}
}
}
Scaffold(
topBar = {
TopBanner(
text = "Choose character",
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(characterList.value) {
CharacterEntry(
icon = BitmapData(
bitmap = it.spriteIdle,
width = it.spriteWidth,
height = it.spriteHeight
)
) {
selectedCharacter = it.id
}
}
}
}
}

View File

@ -0,0 +1,60 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems
@Composable
fun ItemsScreen(
navController: NavController,
) {
var selectedTabItem by remember { mutableStateOf(0) }
val items = listOf(
NavigationItems.MyItems,
NavigationItems.ItemsStore
)
Scaffold(
topBar = {
Column {
TopBanner("Items")
TabRow(
selectedTabIndex = selectedTabItem,
modifier = Modifier
) {
items.forEachIndexed { index, item ->
Tab(
text = { Text(item.label) },
selected = selectedTabItem == index,
onClick = { selectedTabItem = index }
)
}
}
}
}
) { contentPadding ->
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = contentPadding.calculateTopPadding())
) {
when (selectedTabItem) {
0 -> MyItems(navController)
1 -> ItemsStore(navController)
}
}
}
}

View File

@ -0,0 +1,6 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
interface ItemsScreenController {
fun applyItem(itemId: Long, characterId: Long, onCompletion: () -> Unit)
}

View File

@ -0,0 +1,118 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ItemsScreenControllerImpl (
private val context: ComponentActivity,
): ItemsScreenController {
private var database: AppDatabase
enum class ItemTypes(val id: Int) {
PPTraining(1),
HPTraining(2),
APTraining(3),
BPTraining(4),
AllTraining(5),
EvoTimer(6),
LimitTimer(7),
Vitals(8)
}
init {
val application = context.applicationContext as VBHelper
database = application.container.db
}
override fun applyItem(itemId: Long, characterId: Long, onCompletion: () -> Unit) {
context.lifecycleScope.launch {
withContext(Dispatchers.IO) {
val item = getItem(itemId)
val characterData = database.userCharacterDao().getCharacter(characterId)
val beCharacterData: BECharacterData
//var vbCharacterData: VBCharacterData
if (characterData.characterType == DeviceType.BEDevice) {
beCharacterData = database.userCharacterDao().getBeData(characterId)
} else {
TODO("Not implemented")
//vbCharacterData = database.userCharacterDao().getVbData(characterId)
}
if (item.itemIcon in 1 .. 5 && characterData.characterType == DeviceType.BEDevice) {
beCharacterData.itemType = item.itemIcon
beCharacterData.itemMultiplier = 3
beCharacterData.itemRemainingTime = item.itemLength
database
.userCharacterDao()
.updateBECharacterData(beCharacterData)
} else if (item.itemIcon == ItemTypes.EvoTimer.id) {
characterData.transformationCountdown += item.itemLength
if (characterData.transformationCountdown < 0) {
characterData.transformationCountdown = 0
}
// VB does not like it when the transformationCountdown is 0
if (characterData.characterType == DeviceType.VBDevice &&
characterData.transformationCountdown <= 0 ) {
characterData.transformationCountdown = 1
}
database
.userCharacterDao()
.updateCharacter(characterData)
} else if (item.itemIcon == ItemTypes.LimitTimer.id) {
beCharacterData.remainingTrainingTimeInMinutes += item.itemLength
if (beCharacterData.remainingTrainingTimeInMinutes > 6000) {
beCharacterData.remainingTrainingTimeInMinutes = 6000
}
database
.userCharacterDao()
.updateBECharacterData(beCharacterData)
} else if (item.itemIcon == ItemTypes.Vitals.id) {
characterData.vitalPoints += item.itemLength
if (characterData.vitalPoints < 0) {
characterData.vitalPoints = 0
} else if (characterData.vitalPoints > 9999) {
characterData.vitalPoints = 9999
}
database
.userCharacterDao()
.updateCharacter(characterData)
}
consumeItem(item.id)
context.runOnUiThread {
onCompletion()
}
}
}
}
private fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities {
return database
.itemDao()
.getUserItem(itemId)
}
private fun consumeItem(itemId: Long) {
database
.itemDao()
.useItem(itemId)
}
}

View File

@ -0,0 +1,75 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
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.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.setValue
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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun ItemsStore(
navController: NavController
) {
val application = LocalContext.current.applicationContext as VBHelper
val itemsRepository = ItemsRepository(application.container.db)
val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) }
var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(itemsRepository) {
withContext(Dispatchers.IO) {
myItems.value = itemsRepository.getAllItems()
}
}
if (myItems.value.isEmpty()) {
Text("No items")
} else {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
) {
items(myItems.value) { index ->
ItemElement(
itemIcon = getIconResource(index.itemIcon),
lengthIcon = getLengthResource(index.itemLength),
modifier = Modifier
.padding(8.dp),
onClick = {
selectedElementIndex = myItems.value.indexOf(index)
}
)
}
}
if (selectedElementIndex != null) {
ItemDialog(
name = myItems.value[selectedElementIndex!!].name,
description = myItems.value[selectedElementIndex!!].description,
itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon),
lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength),
amount = myItems.value[selectedElementIndex!!].quantity,
onClickUse = { },
onClickCancel = { selectedElementIndex = null }
)
}
}
}

View File

@ -0,0 +1,97 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
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.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
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.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.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
import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun MyItems(
navController: NavController,
) {
val application = LocalContext.current.applicationContext as VBHelper
val itemsRepository = ItemsRepository(application.container.db)
val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) }
var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(itemsRepository) {
withContext(Dispatchers.IO) {
myItems.value = itemsRepository.getUserItems()
}
}
if (myItems.value.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text("No items")
}
} else {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
) {
items(myItems.value) { index ->
ItemElement(
itemIcon = getIconResource(index.itemIcon),
lengthIcon = getLengthResource(index.itemLength),
modifier = Modifier
.padding(8.dp),
onClick = {
selectedElementIndex = myItems.value.indexOf(index)
}
)
}
}
if (selectedElementIndex != null) {
ItemDialog(
name = myItems.value[selectedElementIndex!!].name,
description = myItems.value[selectedElementIndex!!].description,
itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon),
lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength),
amount = myItems.value[selectedElementIndex!!].quantity,
onClickUse = {
navController
.navigate(
NavigationItems
.ApplyItem.route
.replace(
"{itemId}",
myItems.value[selectedElementIndex!!].id.toString()
)
)
selectedElementIndex = null
},
onClickCancel = { selectedElementIndex = null }
)
}
}
}

View File

@ -0,0 +1,17 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.items.Items
import com.github.nacabaro.vbhelper.dtos.ItemDtos
class ItemsRepository(
private val db: AppDatabase
) {
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities> {
return db.itemDao().getAllItems()
}
suspend fun getUserItems(): List<ItemDtos.ItemsWithQuantities> {
return db.itemDao().getAllUserItems()
}
}

View File

@ -0,0 +1,21 @@
<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="m300.57,506.26c0,-5.08 0.1,-11.73 0.29,-19.95 0.39,-8.21 0.68,-15.45 0.88,-21.71 -0.98,1.17 -3.13,3.32 -6.45,6.45 -3.13,2.93 -6.06,5.57 -8.8,7.92l-24.05,19.36L241.02,471.65 308.49,417.97l36.37,0l0,209.44L300.57,627.41Z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m518.57,522.69q0,33.73 -7.33,57.79 -7.04,24.05 -23.17,36.96 -15.84,12.91 -42.83,12.91 -37.84,0 -55.44,-28.45 -17.6,-28.75 -17.6,-79.2 0,-34.03 7.04,-58.08 7.04,-24.05 23.17,-36.96 16.13,-12.91 42.83,-12.91 37.55,0 55.44,28.45 17.89,28.45 17.89,79.49zM416.2,522.69q0,35.79 6.16,53.97 6.16,17.89 22.88,17.89 16.43,0 22.88,-17.89 6.45,-17.89 6.45,-53.97 0,-35.79 -6.45,-53.97 -6.45,-18.19 -22.88,-18.19 -16.72,0 -22.88,18.19 -6.16,18.19 -6.16,53.97z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m600.71,449.94q0,11.73 -0.88,22.59 -0.59,10.85 -1.17,15.25l2.35,0q7.63,-12.32 19.65,-17.89 12.03,-5.57 26.69,-5.57 26.11,0 41.65,14.08 15.84,13.79 15.84,44.59l0,104.43l-43.71,0l0,-93.57q0,-34.61 -25.81,-34.61 -19.65,0 -27.28,13.79 -7.33,13.49 -7.33,39.01l0,75.39l-43.71,0l0,-222.93l43.71,0z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,24 @@
<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="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="m287.83,506.91q0,-7.63 0.29,-19.95 0.59,-12.32 0.88,-21.71 -1.47,1.76 -6.45,6.45 -4.69,4.4 -8.8,7.92L249.7,498.99 228.29,472.3 295.75,418.62l36.37,0l0,209.44l-44.29,0z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
<path
android:pathData="m386.69,628.06l0,-30.8l52.51,-53.09q15.84,-16.43 25.52,-27.28 9.68,-11.15 14.08,-19.95 4.4,-9.09 4.4,-19.36 0,-12.61 -7.04,-18.77 -6.75,-6.16 -18.48,-6.16 -12.03,0 -23.47,5.57Q422.77,463.79 410.15,474.06L386.1,445.61Q395.19,437.69 405.17,430.94 415.43,424.19 428.63,420.09 442.13,415.69 460.9,415.69q20.53,0 35.2,7.63 14.96,7.33 22.88,20.24 8.21,12.61 8.21,28.75 0,17.31 -7.04,31.68 -6.75,14.37 -19.95,28.45 -12.91,14.08 -31.39,31.09l-26.99,25.23l0,2.05l91.23,0l0,37.25z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
<path
android:pathData="m606.98,450.59c0,7.82 -0.29,15.35 -0.88,22.59 -0.39,7.24 -0.78,12.32 -1.17,15.25L607.27,488.43C612.36,480.22 618.91,474.25 626.93,470.54 634.94,466.82 643.84,464.97 653.62,464.97c17.4,0 31.29,4.69 41.65,14.08 10.56,9.19 15.84,24.05 15.84,44.59L711.11,628.06L667.41,628.06l0,-93.57c0,-23.08 -8.6,-34.61 -25.81,-34.61 -13.1,0 -22.2,4.6 -27.28,13.79 -4.89,9 -7.33,22 -7.33,39.01L606.98,628.06L563.27,628.06L563.27,413.88l43.71,0z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,15 @@
<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="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="m337.48,503.79q0,-11.09 0.43,-29.01 0.85,-17.92 1.28,-31.57 -2.13,2.56 -9.39,9.39 -6.83,6.4 -12.8,11.52L282.01,492.27 250.87,453.44 349,375.36L401.91,375.36L401.91,680L337.48,680Z"
android:fillColor="#000000"/>
<path
android:pathData="m594.76,485.44q27.73,0 49.49,10.67 21.76,10.67 34.13,31.15 12.8,20.48 12.8,50.77 0,49.49 -30.72,78.08Q629.75,684.27 569.59,684.27 545.7,684.27 524.36,680 503.46,675.73 487.67,667.63L487.67,612.16q15.79,8.11 37.97,14.08 22.19,5.55 41.81,5.55 28.59,0 43.52,-11.52 15.36,-11.95 15.36,-36.69 0,-46.08 -61.01,-46.08 -11.95,0 -24.75,2.56 -12.8,2.13 -21.33,4.27l-25.6,-13.65 11.52,-155.31l165.12,0l0,54.61L561.48,429.97l-5.55,59.73q7.25,-1.28 15.36,-2.56 8.53,-1.71 23.47,-1.71z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,24 @@
<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="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="M239.19,624.91L239.19,594.11l52.51,-53.09q15.84,-16.43 25.52,-27.28 9.68,-11.15 14.08,-19.95 4.4,-9.09 4.4,-19.36 0,-12.61 -7.04,-18.77 -6.75,-6.16 -18.48,-6.16 -12.03,0 -23.47,5.57 -11.44,5.57 -24.05,15.84L238.6,442.46Q247.7,434.54 257.67,427.79 267.94,421.05 281.14,416.94 294.63,412.54 313.4,412.54q20.53,0 35.2,7.63 14.96,7.33 22.88,20.24 8.21,12.61 8.21,28.75 0,17.31 -7.04,31.68 -6.75,14.37 -19.95,28.45 -12.91,14.08 -31.39,31.09l-26.99,25.23l0,2.05l91.23,0L385.56,624.91Z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
<path
android:pathData="M487.35,624.91L487.35,581.5L397.88,581.5L397.88,550.7L489.7,415.47L530.47,415.47L530.47,547.18L555.7,547.18L555.7,581.5L530.47,581.5L530.47,624.91ZM487.35,497.02q0,-10.27 0.29,-17.01 0.59,-6.75 0.88,-12.03l-1.17,0Q482.36,477.95 474.74,489.39L436.9,547.18L487.35,547.18Z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
<path
android:pathData="m609.48,447.47c0,7.82 -0.29,15.35 -0.88,22.59 -0.39,7.24 -0.78,12.32 -1.17,15.25L609.77,485.31C614.86,477.09 621.41,471.13 629.43,467.41 637.44,463.7 646.34,461.84 656.12,461.84c17.4,0 31.29,4.69 41.65,14.08 10.56,9.19 15.84,24.05 15.84,44.59l0,104.43L669.91,624.93l0,-93.57c0,-23.08 -8.6,-34.61 -25.81,-34.61 -13.1,0 -22.2,4.6 -27.28,13.79 -4.89,9 -7.33,22 -7.33,39.01l0,75.39L565.77,624.93L565.77,414.5l43.71,0z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,15 @@
<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="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="m453.77,437.38q0,31.57 -19.2,50.35 -18.77,18.77 -46.51,25.6l0,1.28q36.69,4.27 55.47,22.19 19.2,17.92 19.2,48.21 0,26.45 -13.23,47.79Q436.71,653.7 409.4,666.07 382.52,678.02 339.85,678.02q-49.49,0 -87.89,-16.64L251.96,606.76q19.63,9.81 40.96,14.93 21.76,5.12 40.11,5.12 34.56,0 48.21,-11.95 14.08,-11.95 14.08,-33.71 0,-12.8 -6.4,-21.33Q382.52,550.87 366.31,546.6 350.52,541.91 321.93,541.91L298.89,541.91l0,-49.49l23.47,0q28.16,0 42.67,-5.12 14.93,-5.55 20.05,-14.51 5.55,-9.39 5.55,-21.33 0,-16.21 -10.24,-25.17 -9.81,-9.39 -33.28,-9.39 -21.76,0 -37.97,7.68 -15.79,7.25 -26.88,14.51L252.39,394.71q17.92,-12.8 41.81,-21.33 24.32,-8.53 57.6,-8.53 46.93,0 74.24,19.2 27.73,18.77 27.73,53.33z"
android:fillColor="#000000"/>
<path
android:pathData="m715.32,521.43q0,49.07 -10.67,84.05 -10.24,34.99 -33.71,53.76Q647.91,678.02 608.66,678.02q-55.04,0 -80.64,-41.39 -25.6,-41.81 -25.6,-115.2 0,-49.49 10.24,-84.48 10.24,-34.99 33.71,-53.76 23.47,-18.77 62.29,-18.77 54.61,0 80.64,41.39 26.03,41.39 26.03,115.63zM566.42,521.43q0,52.05 8.96,78.51 8.96,26.03 33.28,26.03 23.89,0 33.28,-26.03 9.39,-26.03 9.39,-78.51 0,-52.05 -9.39,-78.51 -9.39,-26.45 -33.28,-26.45 -24.32,0 -33.28,26.45 -8.96,26.45 -8.96,78.51z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,17 @@
<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="m381.45,481.69q20.8,0 37.12,8 16.32,8 25.6,23.36 9.6,15.36 9.6,38.08 0,37.12 -23.04,58.56 -23.04,21.12 -68.16,21.12 -17.92,0 -33.92,-3.2 -15.68,-3.2 -27.52,-9.28v-41.6q11.84,6.08 28.48,10.56 16.64,4.16 31.36,4.16 21.44,0 32.64,-8.64 11.52,-8.96 11.52,-27.52 0,-34.56 -45.76,-34.56 -8.96,0 -18.56,1.92 -9.6,1.6 -16,3.2l-19.2,-10.24 8.64,-116.48h123.84v40.96h-81.6l-4.16,44.8q5.44,-0.96 11.52,-1.92 6.4,-1.28 17.6,-1.28z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m540.81,434.01c0,8.53 -0.32,16.75 -0.96,24.64 -0.43,7.89 -0.85,13.44 -1.28,16.64h2.56c5.55,-8.96 12.69,-15.47 21.44,-19.52 8.75,-4.05 18.45,-6.08 29.12,-6.08 18.99,0 34.13,5.12 45.44,15.36 11.52,10.03 17.28,26.24 17.28,48.64v113.92h-47.68v-102.08c0,-25.17 -9.39,-37.76 -28.16,-37.76 -14.29,0 -24.21,5.01 -29.76,15.04 -5.33,9.81 -8,24 -8,42.56v82.24h-47.68v-228.82h47.68z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,15 @@
<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="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="m240.86,541.97q0,-26.45 3.84,-52.05 3.84,-25.6 13.23,-48.21 9.81,-23.04 26.88,-40.53 17.49,-17.92 43.95,-27.73 26.88,-10.24 64.85,-10.24 8.96,0 20.91,0.85 11.95,0.43 20.05,2.13L434.57,417.81q-8.11,-2.13 -17.92,-2.99 -9.39,-1.28 -18.77,-1.28 -37.97,0 -58.88,11.95 -20.48,11.95 -29.01,33.71 -8.53,21.33 -9.81,49.49l2.56,0Q311.26,493.76 327.05,483.52 343.26,473.28 368.86,473.28q40.11,0 63.57,25.17 23.47,25.17 23.47,71.25 0,49.49 -28.16,77.65Q400.01,675.52 352.22,675.52q-31.15,0 -56.32,-14.08Q270.73,646.93 255.79,617.49 240.86,587.62 240.86,541.97ZM350.94,623.89q18.77,0 30.72,-12.8 11.95,-13.23 11.95,-40.53 0,-22.19 -10.24,-34.99 -10.24,-12.8 -31.15,-12.8 -14.08,0 -24.75,6.4 -10.67,5.97 -16.64,15.79 -5.97,9.81 -5.97,20.05 0,14.08 5.12,27.73 5.12,13.23 15.36,22.19 10.67,8.96 25.6,8.96z"
android:fillColor="#000000"/>
<path
android:pathData="m710.62,518.93q0,49.07 -10.67,84.05 -10.24,34.99 -33.71,53.76Q643.21,675.52 603.96,675.52q-55.04,0 -80.64,-41.39 -25.6,-41.81 -25.6,-115.2 0,-49.49 10.24,-84.48 10.24,-34.99 33.71,-53.76 23.47,-18.77 62.29,-18.77 54.61,0 80.64,41.39 26.03,41.39 26.03,115.63zM561.71,518.93q0,52.05 8.96,78.51 8.96,26.03 33.28,26.03 23.89,0 33.28,-26.03 9.39,-26.03 9.39,-78.51 0,-52.05 -9.39,-78.51 -9.39,-26.45 -33.28,-26.45 -24.32,0 -33.28,26.45 -8.96,26.45 -8.96,78.51z"
android:fillColor="#000000"/>
</vector>

View File

@ -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="m296,736 l-56,-56 240,-240 240,240 -56,56 -184,-183 -184,183ZM296,496 L240,440 480,200 720,440 664,496 480,313 296,496Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,12 @@
<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="m664,229.06 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
<path
android:pathData="m664,434.94 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,12 @@
<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="m296,730.94 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
<path
android:pathData="m296,525.06 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
</vector>

View File

@ -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="M440,838q-121,-15 -200.5,-105.5T160,520q0,-66 26,-126.5T260,288l57,57q-38,34 -57.5,79T240,520q0,88 56,155.5T440,758v80ZM520,838v-80q87,-16 143.5,-83T720,520q0,-100 -70,-170t-170,-70h-3l44,44 -56,56 -140,-140 140,-140 56,56 -44,44h3q134,0 227,93t93,227q0,121 -79.5,211.5T520,838Z"
android:fillColor="#000000"/>
</vector>

View File

@ -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="M480,880q-139,-35 -229.5,-159.5T160,444v-244l320,-120 320,120v244q0,152 -90.5,276.5T480,880ZM480,796q104,-33 172,-132t68,-220v-189l-240,-90 -240,90v189q0,121 68,220t172,132ZM480,480Z"
android:fillColor="#000000"/>
</vector>

View File

@ -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="m664,332 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
</vector>

View File

@ -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="m296,628 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,15 @@
<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="m664,332 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
<path
android:pathData="m664,537.89 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
<path
android:pathData="m664,126.11 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,15 @@
<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="m296,628 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
<path
android:pathData="m296,422.11 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
<path
android:pathData="m296,833.89 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
</vector>

View File

@ -1,18 +1,18 @@
[versions]
agp = "8.7.3"
datastore = "1.1.1"
datastore = "1.1.2"
kotlin = "2.0.0"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.9.3"
composeBom = "2024.04.01"
activityCompose = "1.10.0"
composeBom = "2025.01.00"
protobufGradlePlugin = "0.9.4"
protobufJavalite = "4.27.0"
roomRuntime = "2.6.1"
vbNfcReader = "0.1.0"
vbNfcReader = "0.2.0-SNAPSHOT"
dimReader = "2.1.0"
[libraries]