Compare commits

..
Author SHA1 Message Date
Nacho fd76c46f39 Not much
- Imported card backgrounds (not necessary but future proofing just in case)
- Readded the sprite viewer just to verify everything is working as intended, will probably get rid of it again? or maybe I should keep it since it holds a copy of all the sprites in the card...
- TODO: Remove old unused tables
2025-09-14 02:39:52 +02:00
Nacho e85e1f45bf More name changes 2025-09-14 01:09:33 +02:00
Nacho 630cccefe4 Increased AGP version and changed some terminology 2025-09-14 00:49:17 +02:00
92 changed files with 1439 additions and 3433 deletions
+35 -66
View File
@@ -1,69 +1,38 @@
# VBHelper # VBHelper
Application to interact with the Vital series, VB, VH, VBC and VBBE. ## Developer Setup
## Current state of the project 1. Clone vb-nfc-reader (https://github.com/cfogrady/lib-vb-nfc)
2. Run vb-nfc-reader/publishToMavenLocal gradle task in the lib-vb-nfc project.
Right now the project is still under development, and until further notice, any database updates will result in having to erase application data. 3. Clone vb-dim-reader (https://github.com/cfogrady/vb-dim-reader)
4. Run publishToMavenLocal gradle task in the vb-dim-reader project.
This document will be updated once the application does not need any more database resets. 5. Create res/values/keys.xml within the app module.
6. Populate with:
## Features ```
<?xml version="1.0" encoding="utf-8"?>
As of now, the project allows you to read characters, view characters stats, and send them back to your watch. <resources>
<string name="password1">beHmacKey1</string>
You can also apply items to the characters read, such as special missions, or change timers, and store characters in the storage section. <string name="password2">beHmacKey2</string>
<string name="decryptionKey">aesKey</string>
You also earn new items every time an item such as a special mission (VB only) or a character completes an in-app adventure mission. <integer-array name="substitutionArray">
<item>0</item>
App also comes with a dex that will update every time a new character is added, and allows you to see evolution requirements and current adventure stage in the watch. <item>1</item>
<item>2</item>
## How to set up <item>3</item>
<item>4</item>
1. Download the latest version for VB Arena APK from a trustworthy source. If your download is a standalone APK, continue to step 2. Otherwise, if your download is an XAPK, do the following: <item>5</item>
<item>6</item>
1. Using your phone file manager, rename the XAPK file to ZIP, and extract its contents. You can also do this with any other device, such as Windows, macOS and Linux. <item>7</item>
<item>8</item>
2. Once the files are extracted, look for an APK called `com.bandai.vitalbraceletarena.apk`. Copy it somewhere else, you will need it. <item>9</item>
<item>10</item>
2. Install an APK release for VB Arena. You will find the releases [here](http://github.com/nacabaro/vbhelper/releases). Download the latest release and install its APK. <item>11</item>
<item>12</item>
Note, in the current stage of the project, you will have to delete the old application from your device. If the app keeps crashing after installing, clear application data and storage. <item>13</item>
<item>14</item>
3. Import secrets in the app. These secrets will allow the app to talk to the watch. On the main screen, click on the gear icon, then `Import secrets`. <item>15</item>
</integer-array>
You will be prompted to choose a file. Choose the APK file that was previously obtained. </resources>
```
4. Import cards. Due to copyright laws, we cannot offer the characters and sprites themselves in the application. In order to import the cards do the following. 7. Replace the values in the keys.xml file with those extracted from the original app.
8. Run
1. Using your own DiM/BEm cards, dump the cards to your device. You can get an in-depth tutorial in [here](http://mrblinky.net/digimon/vb/dimcardtool/dimcardtool.html). You can download the dump tool from [here](http://mrblinky.net/digimon/vb/dimcardtool/)
2. Once installed the tool and drivers, open the tool, connect your DiM/BEm reader hardware to yout computer and click on Read card.
3. Transfer the resulting file to your mobile device. You can put them anywhere, as long as they are accessible. My recommendation is to put them under a folder called `Cards` in your `Internal storage` or `SD Card`
4. In the app, click on import card. Next choose the BIN file corresponding to the card you want to import.
**Note: if you do not import the card, whenever you attempt to read a character from th watch, the character you read will get deleted.**
5. App will now be ready to be used.
## Planned features
- Online battles, undegoing development by `lightheel`.
- VitalWear compatibility, undergoing development by `cfogrady`.
- Support for multiple languages, not yet started.
- Database backup/restore.
## Credits
- `cyanic` for helping us understand more about the VB connection protocol.
- `cfogrady` for making both [`VB-DIM-Reader`](https://github.com/cfogrady/VB-DIM-Reader) and [`lib-vb-nfc`](https://github.com/cfogrady/lib-vb-nfc)
- `lightheel` for working on the online component in the application, both server and battle client.
- `shvstrz` for the app icon.
+19 -28
View File
@@ -1,23 +1,21 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
id("com.google.devtools.ksp") version "2.3.0" id("com.google.devtools.ksp") version "2.0.21-1.0.27"
id("com.google.protobuf") id("com.google.protobuf")
} }
android { android {
namespace = "com.github.nacabaro.vbhelper" namespace = "com.github.nacabaro.vbhelper"
compileSdk = 36 compileSdk = 35
defaultConfig { defaultConfig {
applicationId = "com.github.nacabaro.vbhelper" applicationId = "com.github.nacabaro.vbhelper"
minSdk = 28 minSdk = 28
targetSdk = 36 targetSdk = 35
versionCode = 1 versionCode = 1
versionName = "Alpha 0.6.3.2" versionName = "Alpha 0.6.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -35,7 +33,9 @@ android {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
} }
kotlinOptions {
jvmTarget = "11"
}
buildFeatures { buildFeatures {
compose = true compose = true
} }
@@ -45,17 +45,14 @@ android {
} }
} }
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
protobuf { protobuf {
protoc { protoc {
artifact = "com.google.protobuf:protoc:4.27.0" artifact = "com.google.protobuf:protoc:4.27.0"
} }
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks { generateProtoTasks {
all().forEach { task -> all().forEach { task ->
task.builtins { task.builtins {
@@ -71,13 +68,10 @@ dependencies {
implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.runtime)
implementation(libs.vb.nfc.reader) implementation(libs.vb.nfc.reader)
implementation(libs.dim.reader) implementation(libs.dim.reader)
ksp(libs.androidx.room.compiler)
annotationProcessor(libs.androidx.room.compiler)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.room.ktx) implementation("androidx.room:room-ktx:2.6.1")
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.navigation.compose)
implementation(libs.material)
implementation(libs.protobuf.javalite)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom)) implementation(platform(libs.androidx.compose.bom))
@@ -86,18 +80,15 @@ dependencies {
implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.androidx.compose.material.icons.extended)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
testImplementation(libs.junit) testImplementation(libs.junit)
ksp(libs.androidx.room.compiler)
annotationProcessor(libs.androidx.room.compiler)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4) androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation("androidx.navigation:navigation-compose:2.7.0")
implementation("com.google.android.material:material:1.2.0")
implementation(libs.protobuf.javalite)
implementation("androidx.compose.material:material")
} }
@@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper package com.github.nacabaro.vbhelper
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
@@ -67,10 +68,13 @@ class MainActivity : ComponentActivity() {
) )
} }
} }
Log.i("MainActivity", "Activity onCreated")
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
Log.i("MainActivity", "onPause")
for(activityListener in onActivityLifecycleListeners) { for(activityListener in onActivityLifecycleListeners) {
activityListener.value.onPause() activityListener.value.onPause()
} }
@@ -78,6 +82,7 @@ class MainActivity : ComponentActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Log.i("MainActivity", "Resume")
for(activityListener in onActivityLifecycleListeners) { for(activityListener in onActivityLifecycleListeners) {
activityListener.value.onResume() activityListener.value.onResume()
} }
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -36,27 +35,20 @@ import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.R import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.utils.getObscuredBitmap import com.github.nacabaro.vbhelper.utils.getObscuredBitmap
import androidx.compose.ui.res.stringResource
@Composable @Composable
fun CharacterEntry( fun CharacterEntry(
icon: BitmapData, icon: BitmapData,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
cardIcon: BitmapData? = null,
obscure: Boolean = false, obscure: Boolean = false,
disabled: Boolean = false, disabled: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium, shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 4, multiplier: Int = 4,
cardColors: CardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
),
onClick: () -> Unit = { } onClick: () -> Unit = { }
) { ) {
val bitmap = remember (icon.bitmap) { val bitmap = remember (icon.bitmap) {
if(obscure) icon.getObscuredBitmap() else icon.getBitmap() if(obscure) icon.getObscuredBitmap() else icon.getBitmap()
} }
val iconSizeMultiplier = 3
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() } val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (icon.width * multiplier / density).dp val dpSize = (icon.width * multiplier / density).dp
@@ -69,8 +61,7 @@ fun CharacterEntry(
}, },
modifier = modifier modifier = modifier
.aspectRatio(1f) .aspectRatio(1f)
.padding(8.dp), .padding(8.dp)
colors = cardColors
) { ) {
Box( Box(
contentAlignment = Alignment.BottomCenter, contentAlignment = Alignment.BottomCenter,
@@ -88,24 +79,7 @@ fun CharacterEntry(
}, },
modifier = Modifier modifier = Modifier
.size(dpSize) .size(dpSize)
.align(Alignment.BottomCenter)
) )
if (cardIcon != null) {
val bitmap = remember (icon.bitmap) { cardIcon.getBitmap() }
val iconBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val dpSize = (icon.width * iconSizeMultiplier /density).dp
Image(
bitmap = iconBitmap,
contentDescription = "Card icon",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
.align(Alignment.BottomEnd)
.padding(8.dp)
)
}
} }
} }
} }
@@ -152,27 +126,14 @@ fun ItemDisplay(
fun SpecialMissionsEntry( fun SpecialMissionsEntry(
specialMission: SpecialMissions, specialMission: SpecialMissions,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClickMission: (Long) -> Unit = { }, onClickCard: () -> Unit = { },
onClickCollect: (Long) -> Unit = { }
) { ) {
val textValue = when (specialMission.missionType) { val textValue = when (specialMission.missionType) {
SpecialMission.Type.NONE -> stringResource(R.string.special_mission_none) SpecialMission.Type.NONE -> "No mission selected"
SpecialMission.Type.STEPS -> stringResource( SpecialMission.Type.STEPS -> "Walk ${specialMission.goal} steps"
R.string.special_mission_steps, SpecialMission.Type.BATTLES -> "Battle ${specialMission.goal} times"
specialMission.goal SpecialMission.Type.WINS -> "Win ${specialMission.goal} battles"
) SpecialMission.Type.VITALS -> "Earn ${specialMission.goal} vitals"
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles,
specialMission.goal
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins,
specialMission.goal
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals,
specialMission.goal
)
} }
val progress = if (specialMission.status == SpecialMission.Status.COMPLETED) { val progress = if (specialMission.status == SpecialMission.Status.COMPLETED) {
@@ -183,22 +144,10 @@ fun SpecialMissionsEntry(
val completion = when (specialMission.missionType) { val completion = when (specialMission.missionType) {
SpecialMission.Type.NONE -> "" SpecialMission.Type.NONE -> ""
SpecialMission.Type.STEPS -> stringResource( SpecialMission.Type.STEPS -> "Walked $progress steps"
R.string.special_mission_steps_progress, SpecialMission.Type.BATTLES -> "Battled $progress times"
progress SpecialMission.Type.WINS -> "Won $progress battles"
) SpecialMission.Type.VITALS -> "Earned $progress vitals"
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles_progress,
progress
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins_progress,
progress
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals_progress,
progress
)
} }
val icon = when (specialMission.missionType) { val icon = when (specialMission.missionType) {
@@ -220,12 +169,10 @@ fun SpecialMissionsEntry(
Card( Card(
modifier = modifier, modifier = modifier,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
onClick = if (specialMission.status == SpecialMission.Status.COMPLETED) { onClick = if (specialMission.status == SpecialMission.Status.COMPLETED || specialMission.status == SpecialMission.Status.FAILED) {
{ onClickCollect(specialMission.id) } onClickCard
} else if (specialMission.status == SpecialMission.Status.UNAVAILABLE) {
{ }
} else { } else {
{ onClickMission(specialMission.id) } { }
}, },
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = color containerColor = color
@@ -241,7 +188,7 @@ fun SpecialMissionsEntry(
) { ) {
Icon( Icon(
painter = painterResource(icon), painter = painterResource(icon),
contentDescription = stringResource(R.string.special_mission_icon_content_description), contentDescription = "Vitals",
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.padding(16.dp) .padding(16.dp)
@@ -3,7 +3,6 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
@@ -38,7 +37,7 @@ interface AdventureDao {
JOIN Adventure a ON uc.id = a.characterId JOIN Adventure a ON uc.id = a.characterId
""" """
) )
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>> suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites>
@Query(""" @Query("""
DELETE FROM Adventure DELETE FROM Adventure
@@ -3,7 +3,6 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CardDtos import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface CardAdventureDao { interface CardAdventureDao {
@@ -53,7 +52,7 @@ interface CardAdventureDao {
WHERE WHERE
cc.cardId = :cardId cc.cardId = :cardId
""") """)
fun getAdventureForCard( suspend fun getAdventureForCard(
cardId: Long cardId: Long
): Flow<List<CardDtos.CardAdventureWithSprites>> ): List<CardDtos.CardAdventureWithSprites>
} }
@@ -0,0 +1,24 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.CardBackground
@Dao
interface CardBackgroundDao {
@Insert
suspend fun insertCardBackground(vararg cardBackground: CardBackground)
@Query("""
SELECT * FROM CardBackground
WHERE cardId = :cardId
""")
suspend fun getCardBackground(cardId: Long): CardBackground
@Query("""
SELECT * FROM CardBackground
""")
suspend fun getBackgrounds(): List<CardBackground>
}
@@ -5,8 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.dtos.CardDtos import com.github.nacabaro.vbhelper.domain.card.CardSprites
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface CardDao { interface CardDao {
@@ -28,7 +27,7 @@ interface CardDao {
WHERE uc.id = :id WHERE uc.id = :id
""" """
) )
fun getCardByCharacterId(id: Long): Flow<Card> suspend fun getCardByCharacterId(id: Long): Card
@Query("UPDATE Card SET name = :newName WHERE id = :id") @Query("UPDATE Card SET name = :newName WHERE id = :id")
suspend fun renameCard(id: Int, newName: String) suspend fun renameCard(id: Int, newName: String)
@@ -36,14 +35,10 @@ interface CardDao {
@Query("DELETE FROM Card WHERE id = :id") @Query("DELETE FROM Card WHERE id = :id")
suspend fun deleteCard(id: Long) suspend fun deleteCard(id: Long)
@Query(""" @Insert
SELECT suspend fun insertSprites(vararg sprites: CardSprites)
c.logo as cardIcon,
c.logoWidth as cardIconWidth, @Query("SELECT * FROM CardSprites")
c.logoHeight as cardIconHeight suspend fun getSprites(): List<CardSprites>
FROM Card c
JOIN CardCharacter cc ON cc.cardId = c.id
WHERE cc.id = :charaId
""")
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon>
} }
@@ -2,47 +2,33 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface CardFusionsDao { interface CardFusionsDao {
@Query(""" @Query(
"""
INSERT INTO INSERT INTO
CardFusions ( CardFusions (
fromCharaId, fromCharaId,
attribute, toVirusFusion,
toCharaId toDataFusion,
toVaccineFusion,
toFreeFusion
) )
SELECT SELECT
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :fromCharaId), (SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :fromCharaId),
:attribute, (SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaIdVirus),
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaId) (SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaIdData),
""") (SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaIdVaccine),
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaIdFree)
"""
)
suspend fun insertNewFusion( suspend fun insertNewFusion(
cardId: Long, cardId: Long,
fromCharaId: Int, fromCharaId: Int,
attribute: NfcCharacter.Attribute, toCharaIdVirus: Int,
toCharaId: Int toCharaIdData: Int,
toCharaIdVaccine: Int,
toCharaIdFree: Int
) )
@Query("""
SELECT
cf.toCharaId as charaId,
cf.fromCharaId as fromCharaId,
s.spriteIdle1 as spriteIdle,
cc.attribute as attribute,
s.width as spriteWidth,
s.height as spriteHeight,
d.discoveredOn as discoveredOn,
cf.attribute as fusionAttribute
FROM CardFusions cf
JOIN CardCharacter cc ON cc.id = cf.toCharaId
JOIN Sprite s ON s.id = cc.id
LEFT JOIN Dex d ON d.id = cc.id
WHERE cf.fromCharaId = :charaId
ORDER BY cc.charaIndex
""")
fun getFusionsForCharacter(charaId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>>
} }
@@ -1,29 +1,17 @@
package com.github.nacabaro.vbhelper.daos package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.card.CardProgress import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface CardProgressDao { interface CardProgressDao {
@Query(""" @Upsert
UPDATE CardProgress fun updateDimProgress(vararg cardProgresses: CardProgress)
SET
currentStage = :currentStage,
unlocked = :unlocked
WHERE cardId = :cardId AND
currentStage < :currentStage
""")
fun updateCardProgress(currentStage: Int, cardId: Long, unlocked: Boolean)
@Query( @Query(
"SELECT currentStage FROM CardProgress WHERE cardId = :cardId" "SELECT currentStage FROM CardProgress WHERE cardId = :cardId"
) )
fun getCardProgress(cardId: Long): Flow<Int> fun getCardProgress(cardId: Long): Int
@Insert
fun insertCardProgress(cardProgress: CardProgress)
} }
@@ -6,7 +6,6 @@ import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.CardCharacter import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.characters.Sprite import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface CharacterDao { interface CharacterDao {
@@ -25,12 +24,10 @@ interface CharacterDao {
d.cardId as cardId, d.cardId as cardId,
c.charaIndex as charId, c.charaIndex as charId,
c.stage as stage, c.stage as stage,
c.attribute as attribute, c.attribute as attribute
cp.currentStage as currentStage
FROM CardCharacter c FROM CardCharacter c
JOIN UserCharacter uc ON c.id = uc.charId JOIN UserCharacter uc ON c.id = uc.charId
JOIN Card d ON c.cardId = d.id JOIN Card d ON c.cardId = d.id
JOIN CardProgress cp ON d.id = cp.cardId
WHERE c.id = :charId WHERE c.id = :charId
""" """
) )
@@ -38,7 +35,7 @@ interface CharacterDao {
@Query( @Query(
""" """
INSERT INTO PossibleTransformations (charaId, requiredVitals, requiredTrophies, requiredBattles, requiredWinRate, changeTimerHours, requiredAdventureLevelCompleted, toCharaId) INSERT INTO CardTransformations (charaId, requiredVitals, requiredTrophies, requiredBattles, requiredWinRate, changeTimerHours, requiredAdventureLevelCompleted, toCharaId)
SELECT SELECT
(SELECT id FROM CardCharacter WHERE charaIndex = :fromChraraIndex AND cardId = :cardId), (SELECT id FROM CardCharacter WHERE charaIndex = :fromChraraIndex AND cardId = :cardId),
:requiredVitals, :requiredVitals,
@@ -78,13 +75,13 @@ interface CharacterDao {
pt.changeTimerHours as changeTimerHours, pt.changeTimerHours as changeTimerHours,
pt.requiredAdventureLevelCompleted as requiredAdventureLevelCompleted pt.requiredAdventureLevelCompleted as requiredAdventureLevelCompleted
FROM FROM
PossibleTransformations pt CardTransformations pt
JOIN CardCharacter c on pt.toCharaId = c.id JOIN CardCharacter c on pt.toCharaId = c.id
JOIN Sprite s ON s.id = c.spriteId JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Dex d ON d.id = pt.toCharaId LEFT JOIN Dex d ON d.id = pt.toCharaId
WHERE WHERE
pt.charaId = :characterId c.cardId = :cardId
""" """
) )
fun getEvolutionRequirementsForCard(characterId: Long): Flow<List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>> suspend fun getEvolutionRequirementsForCard(cardId: Long): List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>
} }
@@ -4,7 +4,6 @@ import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CardDtos import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface DexDao { interface DexDao {
@@ -41,7 +40,7 @@ interface DexDao {
WHERE c.cardId = :cardId WHERE c.cardId = :cardId
""" """
) )
fun getSingleCardProgress(cardId: Long): Flow<List<CharacterDtos.CardCharaProgress>> suspend fun getSingleCardProgress(cardId: Long): List<CharacterDtos.CardCharaProgress>
@Query( @Query(
""" """
@@ -56,5 +55,5 @@ interface DexDao {
FROM Card c FROM Card c
""" """
) )
fun getCardsWithProgress(): Flow<List<CardDtos.CardProgress>> suspend fun getCardsWithProgress(): List<CardDtos.CardProgress>
} }
@@ -3,7 +3,6 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface ItemDao { interface ItemDao {
@@ -14,7 +13,7 @@ interface ItemDao {
ORDER BY Items.itemIcon ASC ORDER BY Items.itemIcon ASC
""" """
) )
fun getAllItems(): Flow<List<ItemDtos.ItemsWithQuantities>> suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities>
@Query( @Query(
""" """
@@ -23,7 +22,7 @@ interface ItemDao {
WHERE quantity > 0 WHERE quantity > 0
""" """
) )
fun getAllUserItems(): Flow<List<ItemDtos.ItemsWithQuantities>> suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>
@Query( @Query(
""" """
@@ -2,8 +2,6 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface SpecialMissionDao { interface SpecialMissionDao {
@@ -14,11 +12,4 @@ interface SpecialMissionDao {
WHERE id = :id WHERE id = :id
""") """)
suspend fun clearSpecialMission(id: Long) suspend fun clearSpecialMission(id: Long)
@Query("""
SELECT *
FROM SpecialMissions
WHERE id = :id
""")
fun getSpecialMission(id: Long): Flow<SpecialMissions>
} }
@@ -13,7 +13,6 @@ import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface UserCharacterDao { interface UserCharacterDao {
@@ -53,7 +52,7 @@ interface UserCharacterDao {
WHERE monId = :monId WHERE monId = :monId
""" """
) )
fun getTransformationHistory(monId: Long): Flow<List<CharacterDtos.TransformationHistory>> suspend fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?
@Query( @Query(
""" """
@@ -69,8 +68,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure, a.characterId = uc.id as isInAdventure
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId JOIN Card d ON d.id = c.cardId
@@ -78,7 +76,7 @@ interface UserCharacterDao {
LEFT JOIN Adventure a ON a.characterId = uc.id LEFT JOIN Adventure a ON a.characterId = uc.id
""" """
) )
fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>> suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
@Query( @Query(
""" """
@@ -94,8 +92,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure, a.characterId = uc.id as isInAdventure
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = d.id JOIN Card d ON c.cardId = d.id
@@ -110,13 +107,13 @@ interface UserCharacterDao {
suspend fun getCharacter(id: Long): UserCharacter suspend fun getCharacter(id: Long): UserCharacter
@Query("SELECT * FROM BECharacterData WHERE id = :id") @Query("SELECT * FROM BECharacterData WHERE id = :id")
fun getBeData(id: Long): Flow<BECharacterData> suspend fun getBeData(id: Long): BECharacterData
@Query("SELECT * FROM VBCharacterData WHERE id = :id") @Query("SELECT * FROM VBCharacterData WHERE id = :id")
fun getVbData(id: Long): Flow<VBCharacterData> suspend fun getVbData(id: Long): VBCharacterData
@Query("SELECT * FROM SpecialMissions WHERE characterId = :id") @Query("SELECT * FROM SpecialMissions WHERE characterId = :id")
fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>> suspend fun getSpecialMissions(id: Long): List<SpecialMissions>
@Query( @Query(
""" """
@@ -132,8 +129,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId as isInAdventure, a.characterId as isInAdventure
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = d.id JOIN Card d ON c.cardId = d.id
@@ -143,7 +139,7 @@ interface UserCharacterDao {
LIMIT 1 LIMIT 1
""" """
) )
fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?> suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites?
@Query("DELETE FROM UserCharacter WHERE id = :id") @Query("DELETE FROM UserCharacter WHERE id = :id")
fun deleteCharacterById(id: Long) fun deleteCharacterById(id: Long)
@@ -197,8 +193,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure, a.characterId = uc.id as isInAdventure
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId JOIN Card d ON d.id = c.cardId
@@ -223,8 +218,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure, a.characterId = uc.id as isInAdventure
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId JOIN Card d ON d.id = c.cardId
@@ -4,6 +4,7 @@ import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.daos.AdventureDao import com.github.nacabaro.vbhelper.daos.AdventureDao
import com.github.nacabaro.vbhelper.daos.CardAdventureDao import com.github.nacabaro.vbhelper.daos.CardAdventureDao
import com.github.nacabaro.vbhelper.daos.CardBackgroundDao
import com.github.nacabaro.vbhelper.daos.CharacterDao import com.github.nacabaro.vbhelper.daos.CharacterDao
import com.github.nacabaro.vbhelper.daos.DexDao import com.github.nacabaro.vbhelper.daos.DexDao
import com.github.nacabaro.vbhelper.daos.CardDao import com.github.nacabaro.vbhelper.daos.CardDao
@@ -13,13 +14,14 @@ import com.github.nacabaro.vbhelper.daos.ItemDao
import com.github.nacabaro.vbhelper.daos.SpecialMissionDao import com.github.nacabaro.vbhelper.daos.SpecialMissionDao
import com.github.nacabaro.vbhelper.daos.SpriteDao import com.github.nacabaro.vbhelper.daos.SpriteDao
import com.github.nacabaro.vbhelper.daos.UserCharacterDao import com.github.nacabaro.vbhelper.daos.UserCharacterDao
import com.github.nacabaro.vbhelper.domain.card.Background import com.github.nacabaro.vbhelper.domain.card.CardBackground
import com.github.nacabaro.vbhelper.domain.card.CardCharacter import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardAdventure import com.github.nacabaro.vbhelper.domain.card.CardAdventure
import com.github.nacabaro.vbhelper.domain.card.CardFusions import com.github.nacabaro.vbhelper.domain.card.CardFusions
import com.github.nacabaro.vbhelper.domain.card.CardProgress import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.card.PossibleTransformations import com.github.nacabaro.vbhelper.domain.card.CardSprites
import com.github.nacabaro.vbhelper.domain.card.CardTransformations
import com.github.nacabaro.vbhelper.domain.characters.Sprite import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.domain.characters.Adventure import com.github.nacabaro.vbhelper.domain.characters.Adventure
import com.github.nacabaro.vbhelper.domain.characters.Dex import com.github.nacabaro.vbhelper.domain.characters.Dex
@@ -39,6 +41,7 @@ import com.github.nacabaro.vbhelper.domain.items.Items
CardCharacter::class, CardCharacter::class,
CardAdventure::class, CardAdventure::class,
CardFusions::class, CardFusions::class,
CardSprites::class,
Sprite::class, Sprite::class,
UserCharacter::class, UserCharacter::class,
BECharacterData::class, BECharacterData::class,
@@ -49,8 +52,8 @@ import com.github.nacabaro.vbhelper.domain.items.Items
Dex::class, Dex::class,
Items::class, Items::class,
Adventure::class, Adventure::class,
Background::class, CardBackground::class,
PossibleTransformations::class CardTransformations::class
] ]
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
@@ -65,4 +68,5 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun specialMissionDao(): SpecialMissionDao abstract fun specialMissionDao(): SpecialMissionDao
abstract fun cardAdventureDao(): CardAdventureDao abstract fun cardAdventureDao(): CardAdventureDao
abstract fun cardFusionsDao(): CardFusionsDao abstract fun cardFusionsDao(): CardFusionsDao
abstract fun cardBackgroundDao(): CardBackgroundDao
} }
@@ -1,11 +1,9 @@
package com.github.nacabaro.vbhelper.di package com.github.nacabaro.vbhelper.di
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
interface AppContainer { interface AppContainer {
val db: AppDatabase val db: AppDatabase
val dataStoreSecretsRepository: DataStoreSecretsRepository val dataStoreSecretsRepository: DataStoreSecretsRepository
val currencyRepository: CurrencyRepository
} }
@@ -1,27 +1,20 @@
import android.content.Context import android.content.Context
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.dataStore import androidx.datastore.dataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import androidx.room.Room import androidx.room.Room
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.AppContainer import com.github.nacabaro.vbhelper.di.AppContainer
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
import com.github.nacabaro.vbhelper.source.SecretsSerializer import com.github.nacabaro.vbhelper.source.SecretsSerializer
import com.github.nacabaro.vbhelper.source.proto.Secrets import com.github.nacabaro.vbhelper.source.proto.Secrets
private const val SECRETS_DATA_STORE_NAME = "secrets.pb" private const val SECRETS_DATA_STORE_NAME = "secrets.pb"
private const val USER_PREFERENCES_NAME = "user_preferences"
val Context.secretsStore: DataStore<Secrets> by dataStore( val Context.secretsStore: DataStore<Secrets> by dataStore(
fileName = SECRETS_DATA_STORE_NAME, fileName = SECRETS_DATA_STORE_NAME,
serializer = SecretsSerializer serializer = SecretsSerializer
) )
val Context.currencyStore: DataStore<Preferences> by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
class DefaultAppContainer(private val context: Context) : AppContainer { class DefaultAppContainer(private val context: Context) : AppContainer {
override val db: AppDatabase by lazy { override val db: AppDatabase by lazy {
@@ -36,6 +29,5 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore) override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
override val currencyRepository = CurrencyRepository(context.currencyStore)
} }
@@ -1,30 +0,0 @@
package com.github.nacabaro.vbhelper.domain
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = Character::class,
parentColumns = ["id"],
childColumns = ["monId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Character::class,
parentColumns = ["id"],
childColumns = ["nextMon"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Evolutions(
@PrimaryKey val monId: Int,
@PrimaryKey val nextMon: Int,
val trophies: Int,
val vitals: Int,
val totalBattles: Int,
val winRate: Int // Does not need to be a floating point
)
@@ -14,8 +14,8 @@ import androidx.room.PrimaryKey
) )
] ]
) )
data class Background ( data class CardBackground (
@PrimaryKey(autoGenerate = true) val id: Long, @PrimaryKey(autoGenerate = true) val id: Long = 0,
val cardId: Long, val cardId: Long,
val background: ByteArray, val background: ByteArray,
val backgroundWidth: Int, val backgroundWidth: Int,
@@ -3,7 +3,6 @@ package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
@@ -16,7 +15,25 @@ import com.github.cfogrady.vbnfc.data.NfcCharacter
ForeignKey( ForeignKey(
entity = CardCharacter::class, entity = CardCharacter::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["toCharaId"], childColumns = ["toVirusFusion"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["toDataFusion"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["toVaccineFusion"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["toFreeFusion"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
] ]
@@ -24,6 +41,8 @@ import com.github.cfogrady.vbnfc.data.NfcCharacter
data class CardFusions( data class CardFusions(
@PrimaryKey(autoGenerate = true) val id: Long, @PrimaryKey(autoGenerate = true) val id: Long,
val fromCharaId: Long, val fromCharaId: Long,
val attribute: NfcCharacter.Attribute, val toVirusFusion: Long?,
val toCharaId: Long val toDataFusion: Long?,
val toVaccineFusion: Long?,
val toFreeFusion: Long?
) )
@@ -0,0 +1,12 @@
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class CardSprites(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val sprite: ByteArray,
val width: Int,
val height: Int
)
@@ -20,7 +20,7 @@ import androidx.room.PrimaryKey
) )
] ]
) )
data class PossibleTransformations ( data class CardTransformations (
@PrimaryKey(autoGenerate = true) val id: Long = 0, @PrimaryKey(autoGenerate = true) val id: Long = 0,
var charaId: Long, var charaId: Long,
val requiredVitals: Int, val requiredVitals: Int,
@@ -24,10 +24,4 @@ object CardDtos {
val characterHp: Int, val characterHp: Int,
val steps: Int, val steps: Int,
) )
data class CardIcon (
val cardIcon: ByteArray,
val cardIconWidth: Int,
val cardIconHeight: Int
)
} }
@@ -31,16 +31,14 @@ object CharacterDtos {
val nameSpriteWidth: Int, val nameSpriteWidth: Int,
val nameSpriteHeight: Int, val nameSpriteHeight: Int,
val isBemCard: Boolean, val isBemCard: Boolean,
val isInAdventure: Boolean, val isInAdventure: Boolean
val active: Boolean
) )
data class CardCharacterInfo( data class CardCharacterInfo(
val cardId: Long, val cardId: Long,
val charId: Int, val charId: Int,
val stage: Int, val stage: Int,
val attribute: NfcCharacter.Attribute, val attribute: NfcCharacter.Attribute
val currentStage: Int
) )
data class TransformationHistory( data class TransformationHistory(
@@ -115,14 +113,4 @@ object CharacterDtos {
val changeTimerHours: Int, val changeTimerHours: Int,
val requiredAdventureLevelCompleted: Int val requiredAdventureLevelCompleted: Int
) )
data class FusionsWithSpritesAndObtained(
val charaId: Long,
val fromCharaId: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val discoveredOn: Long?,
val fusionAttribute: NfcCharacter.Attribute
)
} }
@@ -1,25 +0,0 @@
import android.util.Log
object AppLogger {
private val logs = StringBuilder()
fun d(tag: String, message: String) {
Log.d(tag, message)
append("DEBUG", tag, message)
}
fun e(tag: String, message: String, tr: Throwable? = null) {
Log.e(tag, message, tr)
append("ERROR", tag, message + (tr?.stackTraceToString() ?: ""))
}
private fun append(level: String, tag: String, message: String) {
logs.append("${System.currentTimeMillis()} [$level][$tag] $message\n")
}
fun getLogs(): String = logs.toString()
fun clear() {
logs.clear()
}
}
@@ -1,12 +1,13 @@
package com.github.nacabaro.vbhelper.navigation package com.github.nacabaro.vbhelper.navigation
import android.util.Log
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -36,7 +37,6 @@ import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenContr
import com.github.nacabaro.vbhelper.screens.cardScreen.CardAdventureScreen import com.github.nacabaro.vbhelper.screens.cardScreen.CardAdventureScreen
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen
import com.github.nacabaro.vbhelper.screens.settingsScreen.LoggerScreen
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
@@ -99,14 +99,20 @@ fun AppNavigation(
composable(NavigationItems.Scan.route) { composable(NavigationItems.Scan.route) {
val characterIdString = it.arguments?.getString("characterId") val characterIdString = it.arguments?.getString("characterId")
var characterId by remember { mutableStateOf(characterIdString?.toLongOrNull()) } var characterId by remember { mutableStateOf(characterIdString?.toLongOrNull()) }
Log.d("ScanScreen", "characterId: $characterId")
val launchedFromHomeScreen = (characterIdString?.toLongOrNull() == null) val launchedFromHomeScreen = (characterIdString?.toLongOrNull() == null)
if (characterId == null) { if (characterId == null) {
val context = LocalContext.current.applicationContext as VBHelper val context = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(context.container.db) val storageRepository = StorageRepository(context.container.db)
val characterData by storageRepository.getActiveCharacter().collectAsState(null)
LaunchedEffect(characterId) {
if (characterId == null) {
val characterData = storageRepository.getActiveCharacter()
if (characterData != null) { if (characterData != null) {
characterId = characterData!!.id characterId = characterData.id
}
}
} }
} }
@@ -183,11 +189,6 @@ fun AppNavigation(
) )
} }
} }
composable(NavigationItems.Logger.route) {
LoggerScreen(
navController = navController
)
}
} }
} }
} }
@@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.compose.ui.res.stringResource
@Composable @Composable
fun BottomNavigationBar(navController: NavController) { fun BottomNavigationBar(navController: NavController) {
@@ -26,8 +26,8 @@ fun BottomNavigationBar(navController: NavController) {
items.forEach { item -> items.forEach { item ->
NavigationBarItem ( NavigationBarItem (
icon = { Icon(painter = painterResource(item.icon), contentDescription = stringResource(item.label)) }, icon = { Icon(painter = painterResource(item.icon), contentDescription = item.label) },
label = { Text(text = stringResource(item.label)) }, label = { Text(item.label) },
selected = currentRoute == item.route, selected = currentRoute == item.route,
onClick = { onClick = {
navController.navigate(item.route) { navController.navigate(item.route) {
@@ -1,107 +1,25 @@
package com.github.nacabaro.vbhelper.navigation package com.github.nacabaro.vbhelper.navigation
import com.github.nacabaro.vbhelper.R import com.github.nacabaro.vbhelper.R
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
sealed class NavigationItems ( sealed class NavigationItems (
val route: String, var route: String,
@DrawableRes val icon: Int, var icon: Int,
@StringRes val label: Int var label: String
) { ) {
object Scan : NavigationItems( object Scan : NavigationItems("Scan/{characterId}", R.drawable.baseline_nfc_24, "Scan")
"Scan/{characterId}", object Battles : NavigationItems("Battle", R.drawable.baseline_swords_24, "Battle")
R.drawable.baseline_nfc_24, object Home : NavigationItems("Home", R.drawable.baseline_cottage_24, "Home")
R.string.nav_scan object Dex : NavigationItems("Dex", R.drawable.baseline_menu_book_24, "Dex")
) object CardAdventure : NavigationItems("CardAdventure/{cardId}", R.drawable.baseline_fort_24, "Card adventure")
object Storage : NavigationItems("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
object Battles : NavigationItems( object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings")
"Battle", object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer")
R.drawable.baseline_swords_24, object CardView : NavigationItems("Card/{cardId}", R.drawable.baseline_image_24, "Card")
R.string.nav_battle 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 Home : NavigationItems( object ApplyItem : NavigationItems("ApplyItem/{itemId}", R.drawable.baseline_data_24, "Apply item")
"Home", object Adventure : NavigationItems("Adventure", R.drawable.baseline_fort_24, "Adventure")
R.drawable.baseline_cottage_24, object Credits : NavigationItems("Credits", R.drawable.baseline_data_24, "Credits")
R.string.nav_home
)
object Dex : NavigationItems(
"Dex",
R.drawable.baseline_menu_book_24,
R.string.nav_dex
)
object CardAdventure : NavigationItems(
"CardAdventure/{cardId}",
R.drawable.baseline_fort_24,
R.string.nav_card_adventure
)
object Storage : NavigationItems(
"Storage",
R.drawable.baseline_catching_pokemon_24,
R.string.nav_storage
)
object Settings : NavigationItems(
"Settings",
R.drawable.baseline_settings_24,
R.string.nav_settings
)
object Viewer : NavigationItems(
"Viewer",
R.drawable.baseline_image_24,
R.string.nav_viewer
)
object CardView : NavigationItems(
"Card/{cardId}",
R.drawable.baseline_image_24,
R.string.nav_card
)
object Items : NavigationItems(
"Items",
R.drawable.baseline_data_24,
R.string.nav_items
)
object MyItems : NavigationItems(
"MyItems",
R.drawable.baseline_data_24,
R.string.nav_my_items
)
object ItemsStore : NavigationItems(
"ItemsStore",
R.drawable.baseline_data_24,
R.string.nav_items_store
)
object ApplyItem : NavigationItems(
"ApplyItem/{itemId}",
R.drawable.baseline_data_24,
R.string.nav_apply_item
)
object Adventure : NavigationItems(
"Adventure",
R.drawable.baseline_fort_24,
R.string.nav_adventure
)
object Credits : NavigationItems(
"Credits",
R.drawable.baseline_data_24,
R.string.nav_credits
)
object Logger : NavigationItems(
"Logger",
R.drawable.baseline_bug_report,
R.string.nav_logs
)
} }
@@ -10,16 +10,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun BattlesScreen() { fun BattlesScreen() {
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.battles_online_title) text = "Online battles"
) )
} }
) { contentPadding -> ) { contentPadding ->
@@ -30,7 +27,7 @@ fun BattlesScreen() {
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
.fillMaxSize() .fillMaxSize()
) { ) {
Text(stringResource(R.string.battles_coming_soon)) Text("Coming soon")
} }
} }
} }
@@ -9,16 +9,16 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
@@ -29,25 +29,24 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.Instant import java.time.Instant
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun AdventureScreen( fun AdventureScreen(
navController: NavController, navController: NavController,
storageScreenController: AdventureScreenControllerImpl storageScreenController: AdventureScreenControllerImpl
) { ) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db val database = application.container.db
val storageRepository = StorageRepository(database) val storageRepository = StorageRepository(database)
val characterList by storageRepository.getAdventureCharacters().collectAsState(emptyList()) val characterList = remember {
mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
}
var obtainedItem by remember { var obtainedItem by remember {
mutableStateOf<ItemDtos.PurchasedItem?>(null) mutableStateOf<ItemDtos.PurchasedItem?>(null)
} }
var obtainedCurrency by remember {
mutableStateOf(0)
}
val currentTime by produceState(initialValue = Instant.now().epochSecond) { val currentTime by produceState(initialValue = Instant.now().epochSecond) {
while (true) { while (true) {
@@ -60,17 +59,24 @@ fun AdventureScreen(
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null) mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
} }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
characterList.value = storageRepository
.getAdventureCharacters()
}
}
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.adventure_title), text = "Adventure",
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
) )
} }
) { contentPadding -> ) { contentPadding ->
if (characterList.isEmpty()) { if (characterList.value.isEmpty()) {
Column( Column(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@@ -78,14 +84,14 @@ fun AdventureScreen(
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
.fillMaxSize() .fillMaxSize()
) { ) {
Text(text = stringResource(R.string.adventure_empty_state)) Text(text = "Nothing to see here")
} }
} else { } else {
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
items(characterList) { items(characterList.value) {
AdventureEntry( AdventureEntry(
icon = BitmapData( icon = BitmapData(
bitmap = it.spriteIdle, bitmap = it.spriteIdle,
@@ -96,9 +102,8 @@ fun AdventureScreen(
onClick = { onClick = {
if (it.finishesAdventure < currentTime) { if (it.finishesAdventure < currentTime) {
storageScreenController storageScreenController
.getItemFromAdventure(it.id) { adventureResult, generatedCurrency -> .getItemFromAdventure(it.id) { adventureResult ->
obtainedItem = adventureResult obtainedItem = adventureResult
obtainedCurrency = generatedCurrency
} }
} else { } else {
cancelAdventureDialog = it cancelAdventureDialog = it
@@ -113,7 +118,6 @@ fun AdventureScreen(
if (obtainedItem != null) { if (obtainedItem != null) {
ObtainedItemDialog( ObtainedItemDialog(
obtainedItem = obtainedItem!!, obtainedItem = obtainedItem!!,
obtainedCurrency = obtainedCurrency,
onClickDismiss = { onClickDismiss = {
obtainedItem = null obtainedItem = null
} }
@@ -4,6 +4,6 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface AdventureScreenController { interface AdventureScreenController {
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long) fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem, Int) -> Unit) fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit)
fun cancelAdventure(characterId: Long, onResult: () -> Unit) fun cancelAdventure(characterId: Long, onResult: () -> Unit)
} }
@@ -6,7 +6,6 @@ import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.random.Random import kotlin.random.Random
@@ -38,29 +37,19 @@ class AdventureScreenControllerImpl(
override fun getItemFromAdventure( override fun getItemFromAdventure(
characterId: Long, characterId: Long,
onResult: (ItemDtos.PurchasedItem, Int) -> Unit onResult: (ItemDtos.PurchasedItem) -> Unit
) { ) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) { componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database database
.adventureDao() .adventureDao()
.deleteAdventure(characterId) .deleteAdventure(characterId)
val generatedCurrency = generateRandomCurrency()
val generatedItem = generateItem(characterId) val generatedItem = generateItem(characterId)
onResult(generatedItem, generatedCurrency) onResult(generatedItem)
} }
} }
private suspend fun generateRandomCurrency(): Int {
val currentValue = application.container.currencyRepository.currencyValue.first()
val random = (2..6).random() * 1000
application.container.currencyRepository.setCurrencyValue(currentValue + random)
return random
}
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) { override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) { componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database database
@@ -88,7 +77,6 @@ class AdventureScreenControllerImpl(
val randomItem = database val randomItem = database
.itemDao() .itemDao()
.getAllItems() .getAllItems()
.first()
.random() .random()
val random = ((Random.nextFloat() * character.stage) + 3).roundToInt() val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()
@@ -6,14 +6,15 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import androidx.compose.ui.res.stringResource import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.R import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable @Composable
fun CardAdventureScreen( fun CardAdventureScreen(
@@ -21,17 +22,25 @@ fun CardAdventureScreen(
cardScreenController: CardScreenControllerImpl, cardScreenController: CardScreenControllerImpl,
cardId: Long cardId: Long
) { ) {
val cardAdventureMissions by cardScreenController val cardAdventureMissions = remember { mutableStateOf(emptyList<CardDtos.CardAdventureWithSprites>()) }
var currentCardAdventure = remember { 0 }
LaunchedEffect(cardId) {
withContext(Dispatchers.IO) {
cardAdventureMissions.value =
cardScreenController
.getCardAdventureMissions(cardId) .getCardAdventureMissions(cardId)
.collectAsState(emptyList())
val currentCardAdventure by cardScreenController currentCardAdventure =
cardScreenController
.getCardProgress(cardId) .getCardProgress(cardId)
.collectAsState(0) }
}
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.card_adventure_missions_title), text = "Adventure missions",
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@@ -43,10 +52,10 @@ fun CardAdventureScreen(
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
.verticalScroll(state = rememberScrollState()) .verticalScroll(state = rememberScrollState())
) { ) {
cardAdventureMissions.mapIndexed { index, it -> cardAdventureMissions.value.mapIndexed { index, it ->
CardAdventureEntry( CardAdventureEntry(
cardAdventureEntry = it, cardAdventureEntry = it,
obscure = index > currentCardAdventure - 1 obscure = index > currentCardAdventure
) )
} }
} }
@@ -24,9 +24,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap import com.github.nacabaro.vbhelper.utils.getBitmap
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CardEntry( fun CardEntry(
@@ -78,11 +75,7 @@ fun CardEntry(
modifier = Modifier modifier = Modifier
) )
Text( Text(
text = stringResource( text = "$obtainedCharacters of $totalCharacters characters obtained",
R.string.card_entry_characters_obtained,
obtainedCharacters,
totalCharacters
),
fontFamily = MaterialTheme.typography.labelSmall.fontFamily, fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
fontSize = MaterialTheme.typography.labelSmall.fontSize, fontSize = MaterialTheme.typography.labelSmall.fontSize,
modifier = Modifier modifier = Modifier
@@ -98,7 +91,7 @@ fun CardEntry(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Edit, imageVector = Icons.Default.Edit,
contentDescription = stringResource(R.string.card_entry_edit) contentDescription = "Edit"
) )
} }
IconButton( IconButton(
@@ -106,7 +99,7 @@ fun CardEntry(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Delete, imageVector = Icons.Default.Delete,
contentDescription = stringResource(R.string.card_entry_delete) contentDescription = "Delete"
) )
} }
} }
@@ -1,13 +1,10 @@
package com.github.nacabaro.vbhelper.screens.cardScreen package com.github.nacabaro.vbhelper.screens.cardScreen
import com.github.nacabaro.vbhelper.dtos.CardDtos import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
interface CardScreenController { interface CardScreenController {
fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit) fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit)
fun deleteCard(cardId: Long, onDeleted: () -> Unit) fun deleteCard(cardId: Long, onDeleted: () -> Unit)
fun getCardAdventureMissions(cardId: Long): Flow<List<CardDtos.CardAdventureWithSprites>> suspend fun getCardAdventureMissions(cardId: Long): List<CardDtos.CardAdventureWithSprites>
fun getCardProgress(cardId: Long): Flow<Int> suspend fun getCardProgress(cardId: Long): Int
fun getFusionsForCharacters(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>>
} }
@@ -4,8 +4,6 @@ import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CardDtos import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class CardScreenControllerImpl( class CardScreenControllerImpl(
@@ -34,22 +32,15 @@ class CardScreenControllerImpl(
} }
} }
override fun getCardAdventureMissions(cardId: Long): Flow<List<CardDtos.CardAdventureWithSprites>> { override suspend fun getCardAdventureMissions(cardId: Long): List<CardDtos.CardAdventureWithSprites> {
return database return database
.cardAdventureDao() .cardAdventureDao()
.getAdventureForCard(cardId) .getAdventureForCard(cardId)
} }
override fun getCardProgress(cardId: Long): Flow<Int> { override suspend fun getCardProgress(cardId: Long): Int {
return database return database
.cardProgressDao() .cardProgressDao()
.getCardProgress(cardId) .getCardProgress(cardId)
} }
override fun getFusionsForCharacters(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>> {
return database
.cardFusionsDao()
.getFusionsForCharacter(characterId)
}
} }
@@ -5,12 +5,11 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
@@ -20,25 +19,36 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.DexCharaDetailsDialog import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.DexCharaDetailsDialog
import com.github.nacabaro.vbhelper.source.DexRepository import com.github.nacabaro.vbhelper.source.DexRepository
import com.github.nacabaro.vbhelper.R import kotlinx.coroutines.launch
@Composable @Composable
fun CardViewScreen( fun CardViewScreen(
navController: NavController, navController: NavController,
cardId: Long cardId: Long
) { ) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db) val dexRepository = DexRepository(application.container.db)
val characterList by dexRepository.getCharactersByCardId(cardId).collectAsState(emptyList()) val characterList = remember { mutableStateOf<List<CharacterDtos.CardCharaProgress>>(emptyList()) }
val cardPossibleTransformations = remember { mutableStateOf<List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>>(emptyList()) }
val selectedCharacter = remember { mutableStateOf<CharacterDtos.CardCharaProgress?>(null) } val selectedCharacter = remember { mutableStateOf<CharacterDtos.CardCharaProgress?>(null) }
LaunchedEffect(dexRepository) {
coroutineScope.launch {
val newCharacterList = dexRepository.getCharactersByCardId(cardId)
characterList.value = newCharacterList
val newCardPossibleTransformations = dexRepository.getCardPossibleTransformations(cardId)
cardPossibleTransformations.value = newCardPossibleTransformations
}
}
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.card_view_discovered_characters), text = "Discovered characters",
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
}, },
@@ -60,7 +70,7 @@ fun CardViewScreen(
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
contentPadding = contentPadding contentPadding = contentPadding
) { ) {
items(characterList) { character -> items(characterList.value) { character ->
CharacterEntry( CharacterEntry(
onClick = { onClick = {
selectedCharacter.value = character selectedCharacter.value = character
@@ -78,6 +88,7 @@ fun CardViewScreen(
if (selectedCharacter.value != null) { if (selectedCharacter.value != null) {
DexCharaDetailsDialog( DexCharaDetailsDialog(
currentChara = selectedCharacter.value!!, currentChara = selectedCharacter.value!!,
possibleTransformations = cardPossibleTransformations.value,
obscure = selectedCharacter.value!!.discoveredOn == null, obscure = selectedCharacter.value!!.discoveredOn == null,
onClickClose = { onClickClose = {
selectedCharacter.value = null selectedCharacter.value = null
@@ -1,15 +1,17 @@
package com.github.nacabaro.vbhelper.screens.cardScreen package com.github.nacabaro.vbhelper.screens.cardScreen
import android.util.Log
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -23,17 +25,17 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardDeleteDialog import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardDeleteDialog
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardRenameDialog import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardRenameDialog
import com.github.nacabaro.vbhelper.source.DexRepository import com.github.nacabaro.vbhelper.source.DexRepository
import androidx.compose.ui.res.stringResource import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CardsScreen( fun CardsScreen(
navController: NavController, navController: NavController,
cardScreenController: CardScreenControllerImpl cardScreenController: CardScreenControllerImpl
) { ) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db) val dexRepository = DexRepository(application.container.db)
val cardList by dexRepository.getAllDims().collectAsState(emptyList()) val cardList = remember { mutableStateOf<List<CardDtos.CardProgress>>(emptyList()) }
val selectedCard = remember { mutableStateOf<CardDtos.CardProgress?>(null) } val selectedCard = remember { mutableStateOf<CardDtos.CardProgress?>(null) }
var clickedDelete by remember { mutableStateOf(false) } var clickedDelete by remember { mutableStateOf(false) }
@@ -41,10 +43,17 @@ fun CardsScreen(
var modifyCards by remember { mutableStateOf(false) } var modifyCards by remember { mutableStateOf(false) }
LaunchedEffect(dexRepository) {
coroutineScope.launch {
val newDimList = dexRepository.getAllDims()
cardList.value = newDimList
}
}
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.cards_my_cards_title), text = "My cards",
onModifyClick = { onModifyClick = {
modifyCards = !modifyCards modifyCards = !modifyCards
} }
@@ -55,7 +64,7 @@ fun CardsScreen(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
items(cardList) { items(cardList.value) {
CardEntry( CardEntry(
name = it.cardName, name = it.cardName,
logo = BitmapData( logo = BitmapData(
@@ -97,8 +106,8 @@ fun CardsScreen(
selectedCard.value = null selectedCard.value = null
}, },
onRename = { newName -> onRename = { newName ->
AppLogger.d("CardsScreen", "New name: $newName") Log.d("CardsScreen", "New name: $newName")
AppLogger.d("CardsScreen", "Card: ${selectedCard.value.toString()}") Log.d("CardsScreen", "Card: ${selectedCard.value.toString()}")
cardScreenController cardScreenController
.renameCard( .renameCard(
cardId = selectedCard.value!!.cardId, cardId = selectedCard.value!!.cardId,
@@ -16,11 +16,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
@@ -28,36 +23,22 @@ import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.source.DexRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap import com.github.nacabaro.vbhelper.utils.getImageBitmap
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun DexCharaDetailsDialog( fun DexCharaDetailsDialog(
currentChara: CharacterDtos.CardCharaProgress, currentChara: CharacterDtos.CardCharaProgress,
possibleTransformations: List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>,
obscure: Boolean, obscure: Boolean,
onClickClose: () -> Unit onClickClose: () -> Unit
) { ) {
val nameMultiplier = 3 val nameMultiplier = 3
val charaMultiplier = 4 val charaMultiplier = 4
val application = LocalContext.current.applicationContext as VBHelper val currentCharaPossibleTransformations = possibleTransformations.filter { it.fromCharaId == currentChara.id }
val database = application.container.db
val dexRepository = DexRepository(database)
var showFusions by remember { mutableStateOf(false) }
val currentCharaPossibleTransformations by dexRepository
.getCharacterPossibleTransformations(currentChara.id)
.collectAsState(emptyList())
val currentCharaPossibleFusions by dexRepository
.getCharacterPossibleFusions(currentChara.id)
.collectAsState(emptyList())
val romanNumeralsStage = when (currentChara.stage) { val romanNumeralsStage = when (currentChara.stage) {
1 -> "II" 1 -> "II"
@@ -120,7 +101,7 @@ fun DexCharaDetailsDialog(
) { ) {
Image( Image(
bitmap = charaImageBitmapData.imageBitmap, bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(R.string.dex_chara_icon_description), contentDescription = "Icon",
modifier = Modifier modifier = Modifier
.size(charaImageBitmapData.dpWidth) .size(charaImageBitmapData.dpWidth)
.padding(8.dp), .padding(8.dp),
@@ -139,7 +120,7 @@ fun DexCharaDetailsDialog(
Column { Column {
Image( Image(
bitmap = nameImageBitmapData.imageBitmap, bitmap = nameImageBitmapData.imageBitmap,
contentDescription = stringResource(R.string.dex_chara_name_icon_description), contentDescription = "Icon",
modifier = Modifier modifier = Modifier
.width(nameImageBitmapData.dpWidth) .width(nameImageBitmapData.dpWidth)
.height(nameImageBitmapData.dpHeight), .height(nameImageBitmapData.dpHeight),
@@ -148,28 +129,17 @@ fun DexCharaDetailsDialog(
Spacer(modifier = Modifier.padding(4.dp)) Spacer(modifier = Modifier.padding(4.dp))
if (currentChara.baseHp != 65535) { if (currentChara.baseHp != 65535) {
Text( Text(
text = stringResource( text = "HP: ${currentChara.baseHp}, BP: ${currentChara.baseBp}, AP: ${currentChara.baseAp}"
R.string.dex_chara_stats,
currentChara.baseHp,
currentChara.baseBp,
currentChara.baseAp
)
)
Text(
text = stringResource(
R.string.dex_chara_stage_attribute,
romanNumeralsStage,
currentChara.attribute.toString().substring(0, 2)
)
) )
Text(text = "Stg: ${romanNumeralsStage}, Atr: ${currentChara.attribute.toString().substring(0, 2)}")
} }
} }
} else { } else {
Column { Column {
Text(stringResource(R.string.dex_chara_unknown_name)) Text(text = "????????????????")
Spacer(modifier = Modifier.padding(4.dp)) Spacer(modifier = Modifier.padding(4.dp))
Text(stringResource(R.string.dex_chara_stage_attribute_unknown)) Text(text = "Stg: -, Atr: -")
Text(stringResource(R.string.dex_chara_stats_unknown)) Text(text = "HP: -, BP: -, AP: -")
} }
} }
} }
@@ -210,7 +180,7 @@ fun DexCharaDetailsDialog(
) { ) {
Image( Image(
bitmap = selectedCharaImageBitmap.imageBitmap, bitmap = selectedCharaImageBitmap.imageBitmap,
contentDescription = stringResource(R.string.dex_chara_icon_description), contentDescription = "Icon",
modifier = Modifier modifier = Modifier
.size(selectedCharaImageBitmap.dpWidth) .size(selectedCharaImageBitmap.dpWidth)
.padding(8.dp), .padding(8.dp),
@@ -226,62 +196,20 @@ fun DexCharaDetailsDialog(
.padding(16.dp) .padding(16.dp)
) )
Column { Column {
Text( Text("Tr: ${it.requiredTrophies}; Bt: ${it.requiredBattles}; Vr: ${it.requiredVitals}; Wr: ${it.requiredWinRate}%; Ct: ${it.changeTimerHours}h")
text = stringResource( Text("AdvLvl ${it.requiredAdventureLevelCompleted + 1}")
R.string.dex_chara_requirements,
it.requiredTrophies,
it.requiredBattles,
it.requiredVitals,
it.requiredWinRate,
it.changeTimerHours
)
)
Text(
text = stringResource(
R.string.dex_chara_adventure_level,
it.requiredAdventureLevelCompleted + 1
)
)
} }
} }
} }
} }
} }
Row {
if (currentCharaPossibleFusions.isNotEmpty()) {
Button(
onClick = {
showFusions = true
}
) {
Text(stringResource(R.string.dex_chara_fusions_button))
}
}
Spacer(
modifier = Modifier
.padding(4.dp)
)
Button( Button(
onClick = onClickClose onClick = onClickClose
) { ) {
Text(stringResource(R.string.dex_chara_close_button)) Text("Close")
} }
} }
} }
} }
} }
if (showFusions) {
DexCharaFusionsDialog(
currentChara = currentChara,
currentCharaPossibleFusions = currentCharaPossibleFusions,
onClickDismiss = {
showFusions = false
},
obscure = obscure
)
}
}
@@ -1,191 +0,0 @@
package com.github.nacabaro.vbhelper.screens.cardScreen.dialogs
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.FilterQuality
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.getImageBitmap
@Composable
fun DexCharaFusionsDialog(
currentChara: CharacterDtos.CardCharaProgress,
currentCharaPossibleFusions: List<CharacterDtos.FusionsWithSpritesAndObtained>,
obscure: Boolean,
onClickDismiss: () -> Unit,
) {
val nameMultiplier = 3
val charaMultiplier = 4
val charaBitmapData = BitmapData(
bitmap = currentChara.spriteIdle,
width = currentChara.spriteWidth,
height = currentChara.spriteHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = charaMultiplier,
obscure = obscure
)
val nameBitmapData = BitmapData(
bitmap = currentChara.nameSprite,
width = currentChara.nameSpriteWidth,
height = currentChara.nameSpriteHeight
)
val nameImageBitmapData = nameBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = nameMultiplier,
obscure = obscure
)
Dialog(
onDismissRequest = onClickDismiss,
) {
Card(
modifier = Modifier
.fillMaxWidth()
) {
Column(
modifier = Modifier
.padding(16.dp)
) {
Row {
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
colorFilter = when (obscure) {
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
false -> null
},
filterQuality = FilterQuality.None
)
}
Spacer(
modifier = Modifier
.padding(16.dp)
)
if (!obscure) {
Column {
Image(
bitmap = nameImageBitmapData.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.width(nameImageBitmapData.dpWidth)
.height(nameImageBitmapData.dpHeight),
filterQuality = FilterQuality.None
)
}
} else {
Column {
Text(text = "????????????????")
}
}
}
Spacer(modifier = Modifier.padding(16.dp))
Column {
currentCharaPossibleFusions.map {
val selectedCharaBitmap = BitmapData(
bitmap = it.spriteIdle,
width = it.spriteWidth,
height = it.spriteHeight
)
val selectedCharaImageBitmap = selectedCharaBitmap.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = it.discoveredOn == null
)
Card (
modifier = Modifier
.padding(vertical = 8.dp)
) {
Row (
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
) {
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = selectedCharaImageBitmap.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.size(selectedCharaImageBitmap.dpWidth)
.padding(8.dp),
colorFilter = when (it.discoveredOn == null) {
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
false -> null
},
filterQuality = FilterQuality.None
)
}
Spacer(
modifier = Modifier
.padding(16.dp)
)
Column {
Text("Combine with ${it.fusionAttribute}")
}
}
}
}
}
Button(
onClick = onClickDismiss
) {
Text("Close")
}
}
}
}
}
@@ -8,10 +8,8 @@ import androidx.compose.material3.Card
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun BetaWarning( fun BetaWarning(
@@ -26,21 +24,21 @@ fun BetaWarning(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = stringResource(R.string.beta_warning_message_main) text = "This application is currently in alpha and it is not complete. Do not use to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!"
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Text( Text(
text = stringResource(R.string.beta_warning_message_compatibility) text = "Application should work now with the original VB and the VH."
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Text( Text(
text = stringResource(R.string.beta_warning_message_thanks) text = "Thank you for your understanding and patience. Sincerely, the dev team."
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Button( Button(
onClick = onDismissRequest onClick = onDismissRequest
) { ) {
Text(text = stringResource(R.string.beta_warning_button_dismiss)) Text(text = "Dismiss")
} }
} }
} }
@@ -1,5 +1,6 @@
package com.github.nacabaro.vbhelper.screens.homeScreens package com.github.nacabaro.vbhelper.screens.homeScreens
import android.util.Log
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -11,7 +12,6 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -20,7 +20,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
@@ -29,7 +28,9 @@ import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEBEmHomeScreen import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEBEmHomeScreen
@@ -37,12 +38,8 @@ import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.VBDiMHomeScreen import com.github.nacabaro.vbhelper.screens.homeScreens.screens.VBDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.R import kotlinx.coroutines.Dispatchers
import com.github.nacabaro.vbhelper.dtos.CardDtos import kotlinx.coroutines.withContext
import com.github.nacabaro.vbhelper.source.CardRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.flowOf
import kotlin.collections.emptyList
@Composable @Composable
fun HomeScreen( fun HomeScreen(
@@ -51,59 +48,28 @@ fun HomeScreen(
) { ) {
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db) val storageRepository = StorageRepository(application.container.db)
val cardRepository = CardRepository(application.container.db) val activeMon = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
val activeMon by storageRepository val beData = remember { mutableStateOf<BECharacterData?>(null) }
.getActiveCharacter() val vbData = remember { mutableStateOf<VBCharacterData?>(null) }
.collectAsState(initial = null) val vbSpecialMissions = remember { mutableStateOf<List<SpecialMissions>>(emptyList()) }
val cardIconData by (
activeMon
?.let { chara ->
cardRepository.getCardIconByCharaId(chara.charId)
}
?: flowOf<CardDtos.CardIcon?>(null)
).collectAsState(initial = null)
val transformationHistory by (
activeMon
?.let { chara ->
storageRepository.getTransformationHistory(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbSpecialMissions by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getSpecialMissions(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbData by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getCharacterVbData(chara.id)
}
?: flowOf<VBCharacterData?>(null)
).collectAsState(initial = null)
val beData by (
activeMon
?.takeIf { it.characterType == DeviceType.BEDevice }
?.let { chara ->
storageRepository.getCharacterBeData(chara.id)
}
?: flowOf<BECharacterData?>(null)
).collectAsState(initial = null)
var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) } var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }
var betaWarning by rememberSaveable { mutableStateOf(true) } var betaWarning by rememberSaveable { mutableStateOf(true) }
var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) } var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) }
var collectedCurrency by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(storageRepository, activeMon, collectedItem) {
withContext(Dispatchers.IO) {
activeMon.value = storageRepository.getActiveCharacter()
if (activeMon.value != null && activeMon.value!!.characterType == DeviceType.BEDevice) {
beData.value = storageRepository.getCharacterBeData(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
} else if (activeMon.value != null && activeMon.value!!.characterType == DeviceType.VBDevice) {
vbData.value = storageRepository.getCharacterVbData(activeMon.value!!.id)
vbSpecialMissions.value = storageRepository.getSpecialMissions(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
}
}
}
LaunchedEffect(true) { LaunchedEffect(true) {
homeScreenController homeScreenController
@@ -115,7 +81,7 @@ fun HomeScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.home_title), text = "VB Helper",
onScanClick = { onScanClick = {
navController.navigate(NavigationItems.Scan.route) navController.navigate(NavigationItems.Scan.route)
}, },
@@ -125,7 +91,8 @@ fun HomeScreen(
) )
} }
) { contentPadding -> ) { contentPadding ->
if (activeMon == null || (beData == null && vbData == null) || cardIconData == null || transformationHistory.isEmpty()) { if (activeMon.value == null || (beData.value == null && vbData.value == null) || transformationHistory.value == null) {
Log.d("TetTet", "Something is null")
Column ( Column (
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
@@ -133,44 +100,35 @@ fun HomeScreen(
.fillMaxSize() .fillMaxSize()
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
Text(text = stringResource(R.string.adventure_empty_state)) Text(text = "Nothing to see here")
} }
} else { } else {
val cardIcon = BitmapData( Log.d("TetTet", "Something is not null")
bitmap = cardIconData!!.cardIcon, if (activeMon.value!!.isBemCard) {
width = cardIconData!!.cardIconWidth,
height = cardIconData!!.cardIconHeight
)
if (activeMon!!.isBemCard && beData != null) {
BEBEmHomeScreen( BEBEmHomeScreen(
activeMon = activeMon!!, activeMon = activeMon.value!!,
beData = beData!!, beData = beData.value!!,
transformationHistory = transformationHistory, transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding, contentPadding = contentPadding
cardIcon = cardIcon
) )
} else if (!activeMon!!.isBemCard && activeMon!!.characterType == DeviceType.BEDevice && beData != null) { } else if (!activeMon.value!!.isBemCard && activeMon.value!!.characterType == DeviceType.BEDevice) {
BEDiMHomeScreen( BEDiMHomeScreen(
activeMon = activeMon!!, activeMon = activeMon.value!!,
beData = beData!!, beData = beData.value!!,
transformationHistory = transformationHistory, transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding, contentPadding = contentPadding
cardIcon = cardIcon
) )
} else if (vbData != null) { } else {
VBDiMHomeScreen( VBDiMHomeScreen(
activeMon = activeMon!!, activeMon = activeMon.value!!,
vbData = vbData!!, vbData = vbData.value!!,
transformationHistory = transformationHistory, transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding, contentPadding = contentPadding,
specialMissions = vbSpecialMissions, specialMissions = vbSpecialMissions.value,
homeScreenController = homeScreenController, homeScreenController = homeScreenController,
onClickCollect = { item, currency -> onClickCollect = { item ->
collectedItem = item collectedItem = item
collectedCurrency = currency }
},
cardIcon = cardIcon
) )
} }
} }
@@ -179,10 +137,8 @@ fun HomeScreen(
if (collectedItem != null) { if (collectedItem != null) {
ObtainedItemDialog( ObtainedItemDialog(
obtainedItem = collectedItem!!, obtainedItem = collectedItem!!,
obtainedCurrency = collectedCurrency!!,
onClickDismiss = { onClickDismiss = {
collectedItem = null collectedItem = null
collectedCurrency = null
} }
) )
} }
@@ -197,7 +153,7 @@ fun HomeScreen(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = stringResource(R.string.home_adventure_mission_finished), text = "One of your characters has finished their adventure mission!",
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
Button( Button(
@@ -208,7 +164,7 @@ fun HomeScreen(
.padding(8.dp) .padding(8.dp)
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text(text = stringResource(R.string.beta_warning_button_dismiss)) Text(text = "Dismiss")
} }
} }
} }
@@ -1,8 +1,9 @@
package com.github.nacabaro.vbhelper.screens.homeScreens package com.github.nacabaro.vbhelper.screens.homeScreens
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface HomeScreenController { interface HomeScreenController {
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit) fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit) fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?) -> Unit)
} }
@@ -5,7 +5,6 @@ import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vbnfc.vb.SpecialMission import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.Instant import java.time.Instant
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -23,7 +22,6 @@ class HomeScreenControllerImpl(
val adventureCharacters = database val adventureCharacters = database
.adventureDao() .adventureDao()
.getAdventureCharacters() .getAdventureCharacters()
.first()
val finishedAdventureCharacters = adventureCharacters.filter { character -> val finishedAdventureCharacters = adventureCharacters.filter { character ->
character.finishesAdventure <= currentTime character.finishesAdventure <= currentTime
@@ -33,22 +31,16 @@ class HomeScreenControllerImpl(
} }
} }
override fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit) { override fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?) -> Unit) {
componentActivity.lifecycleScope.launch { componentActivity.lifecycleScope.launch {
val missionStatus = database
.specialMissionDao()
.getSpecialMission(missionId)
.first()
database database
.specialMissionDao() .specialMissionDao()
.clearSpecialMission(missionId) .clearSpecialMission(missionId)
if (missionStatus.status == SpecialMission.Status.COMPLETED) { if (missionCompletion == SpecialMission.Status.COMPLETED) {
val randomItem = database val randomItem = database
.itemDao() .itemDao()
.getAllItems() .getAllItems()
.first()
.random() .random()
val randomItemAmount = (Random.nextFloat() * 5).roundToInt() val randomItemAmount = (Random.nextFloat() * 5).roundToInt()
@@ -70,14 +62,11 @@ class HomeScreenControllerImpl(
itemType = randomItem.itemType itemType = randomItem.itemType
) )
val randomAmount = (2..6).random() * 1000 onCleared(purchasedItem)
val currentCurrency = application.container.currencyRepository.currencyValue.first()
application.container.currencyRepository.setCurrencyValue(currentCurrency + randomAmount)
onCleared(purchasedItem, randomAmount)
} else { } else {
onCleared(null, null) onCleared(null)
} }
} }
} }
} }
@@ -1,56 +0,0 @@
package com.github.nacabaro.vbhelper.screens.homeScreens.dialogs
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.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
@Composable
fun DeleteSpecialMissionDialog(
onClickDismiss: () -> Unit,
onClickDelete: () -> Unit
) {
Dialog(
onDismissRequest = onClickDismiss
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = stringResource(R.string.home_special_mission_delete_main),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.padding(8.dp))
Row {
Button(
onClick = onClickDismiss,
modifier = Modifier
.padding(8.dp)
) {
Text(text = stringResource(R.string.home_special_mission_delete_dismiss))
}
Button(
onClick = onClickDelete,
modifier = Modifier
.padding(8.dp)
) {
Text(text = stringResource(R.string.home_special_mission_delete_remove))
}
}
}
}
}
}
@@ -26,7 +26,6 @@ import java.util.Locale
fun BEBEmHomeScreen( fun BEBEmHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites, activeMon: CharacterDtos.CharacterWithSprites,
beData: BECharacterData, beData: BECharacterData,
cardIcon: BitmapData,
transformationHistory: List<CharacterDtos.TransformationHistory>, transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues contentPadding: PaddingValues
) { ) {
@@ -47,7 +46,6 @@ fun BEBEmHomeScreen(
), ),
multiplier = 8, multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
cardIcon = cardIcon,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -26,7 +26,6 @@ import kotlin.text.format
@Composable @Composable
fun BEDiMHomeScreen( fun BEDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites, activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
beData: BECharacterData, beData: BECharacterData,
transformationHistory: List<CharacterDtos.TransformationHistory>, transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues contentPadding: PaddingValues
@@ -46,7 +45,6 @@ fun BEDiMHomeScreen(
width = activeMon.spriteWidth, width = activeMon.spriteWidth,
height = activeMon.spriteHeight height = activeMon.spriteHeight
), ),
cardIcon = cardIcon,
multiplier = 8, multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier modifier = Modifier
@@ -10,10 +10,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -29,23 +25,17 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import java.util.Locale import java.util.Locale
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.screens.homeScreens.dialogs.DeleteSpecialMissionDialog
@Composable @Composable
fun VBDiMHomeScreen( fun VBDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites, activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
vbData: VBCharacterData, vbData: VBCharacterData,
specialMissions: List<SpecialMissions>, specialMissions: List<SpecialMissions>,
homeScreenController: HomeScreenControllerImpl, homeScreenController: HomeScreenControllerImpl,
transformationHistory: List<CharacterDtos.TransformationHistory>, transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
onClickCollect: (ItemDtos.PurchasedItem?, Int?) -> Unit onClickCollect: (ItemDtos.PurchasedItem?) -> Unit
) { ) {
var selectedSpecialMissionId by remember { mutableStateOf<Long>(-1) }
Column( Column(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
@@ -61,7 +51,6 @@ fun VBDiMHomeScreen(
width = activeMon.spriteWidth, width = activeMon.spriteWidth,
height = activeMon.spriteHeight height = activeMon.spriteHeight
), ),
cardIcon = cardIcon,
multiplier = 8, multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier modifier = Modifier
@@ -76,7 +65,7 @@ fun VBDiMHomeScreen(
ItemDisplay( ItemDisplay(
icon = R.drawable.baseline_vitals_24, icon = R.drawable.baseline_vitals_24,
textValue = activeMon.vitalPoints.toString(), textValue = activeMon.vitalPoints.toString(),
definition = stringResource(R.string.home_vbdim_vitals), definition = "Vitals",
modifier = Modifier modifier = Modifier
.weight(0.5f) .weight(0.5f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -85,7 +74,7 @@ fun VBDiMHomeScreen(
ItemDisplay( ItemDisplay(
icon = R.drawable.baseline_trophy_24, icon = R.drawable.baseline_trophy_24,
textValue = activeMon.trophies.toString(), textValue = activeMon.trophies.toString(),
definition = stringResource(R.string.home_vbdim_trophies), definition = "Trophies",
modifier = Modifier modifier = Modifier
.weight(0.5f) .weight(0.5f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -100,7 +89,7 @@ fun VBDiMHomeScreen(
ItemDisplay( ItemDisplay(
icon = R.drawable.baseline_mood_24, icon = R.drawable.baseline_mood_24,
textValue = activeMon.mood.toString(), textValue = activeMon.mood.toString(),
definition = stringResource(R.string.home_vbdim_mood), definition = "Mood",
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -113,7 +102,7 @@ fun VBDiMHomeScreen(
0 -> "${activeMon.transformationCountdown} m" 0 -> "${activeMon.transformationCountdown} m"
else -> "$transformationCountdownInHours h" else -> "$transformationCountdownInHours h"
}, },
definition = stringResource(R.string.home_vbdim_next_timer), definition = "Next timer",
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -133,7 +122,7 @@ fun VBDiMHomeScreen(
) + " %" // Specify locale ) + " %" // Specify locale
} }
}, },
definition = stringResource(R.string.home_vbdim_total_battle_win), definition = "Total battle win %",
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -153,7 +142,7 @@ fun VBDiMHomeScreen(
) + " %" // Specify locale ) + " %" // Specify locale
} }
}, },
definition = stringResource(R.string.home_vbdim_current_phase_win), definition = "Current phase win %",
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -176,7 +165,7 @@ fun VBDiMHomeScreen(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = stringResource(R.string.home_vbdim_special_missions), text = "Special missions",
fontSize = 24.sp fontSize = 24.sp
) )
} }
@@ -190,28 +179,11 @@ fun VBDiMHomeScreen(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(8.dp), .padding(8.dp),
onClickMission = { missionId -> ) {
selectedSpecialMissionId = missionId
},
onClickCollect = {
homeScreenController homeScreenController
.clearSpecialMission(selectedSpecialMissionId, onClickCollect) .clearSpecialMission(mission.id, mission.status, onClickCollect)
}
)
} }
} }
} }
if (selectedSpecialMissionId.toInt() != -1) {
DeleteSpecialMissionDialog(
onClickDismiss = {
selectedSpecialMissionId = -1
},
onClickDelete = {
homeScreenController
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
selectedSpecialMissionId = -1
}
)
} }
} }
@@ -15,7 +15,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
@@ -25,9 +24,8 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ChooseCharacterScreen( fun ChooseCharacterScreen(
@@ -37,7 +35,7 @@ fun ChooseCharacterScreen(
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db, ) val storageRepository = StorageRepository(application.container.db)
val characterList = remember { val characterList = remember {
mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList())
} }
@@ -59,7 +57,7 @@ fun ChooseCharacterScreen(
characterList.value = storageRepository.getVBCharacters() characterList.value = storageRepository.getVBCharacters()
} }
else -> { else -> {
characterList.value = storageRepository.getAllCharacters().first() characterList.value = storageRepository.getAllCharacters()
} }
} }
} }
@@ -70,7 +68,7 @@ fun ChooseCharacterScreen(
itemsScreenController.applyItem(itemId, selectedCharacter!!) { itemsScreenController.applyItem(itemId, selectedCharacter!!) {
Toast.makeText( Toast.makeText(
application.applicationContext, application.applicationContext,
application.getString(R.string.choose_character_item_applied), "Item applied!",
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack() navController.popBackStack()
@@ -81,7 +79,7 @@ fun ChooseCharacterScreen(
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.choose_character_title), text = "Choose character",
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@@ -1,173 +0,0 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
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.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.res.stringResource
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.domain.items.ItemType
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@Composable
fun ItemDialog(
item: ItemDtos.ItemsWithQuantities,
onClickCancel: () -> Unit,
onClickUse: (() -> Unit)? = null,
onClickPurchase: (() -> Unit)? = null,
) {
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 = getIconResource(item.itemIcon)),
contentDescription = null,
modifier = Modifier
.size(96.dp)
.align(Alignment.Center)
)
Icon(
painter = painterResource(id = getLengthResource(item.itemLength)),
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 = item.name,
modifier = Modifier
.fillMaxWidth()
)
}
}
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
text = item.description,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
)
if (onClickPurchase != null) {
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.item_dialog_costs_credits,
item.price
),
modifier = Modifier
.fillMaxWidth()
)
}
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.item_dialog_you_have_quantity,
item.quantity
),
modifier = Modifier
.fillMaxWidth()
)
Row (
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
) {
if (onClickUse != null) {
Button(
onClick = onClickUse
) {
Text(stringResource(R.string.item_dialog_use))
}
}
if (onClickPurchase != null) {
Button(
onClick = onClickPurchase
) {
Text(stringResource(R.string.item_dialog_purchase))
}
}
Spacer(modifier = Modifier.size(8.dp))
Button(
onClick = onClickCancel
) {
Text(stringResource(R.string.item_dialog_cancel))
}
}
}
}
}
}
@Composable
@Preview(showBackground = true)
fun PreviewItemDialog() {
VBHelperTheme {
ItemDialog(
item = ItemDtos.ItemsWithQuantities(
name = "AP Training x3 (60 min)",
description = "Boosts AP during training (for 60 minutes)",
itemIcon = R.drawable.baseline_attack_24,
itemLength = R.drawable.baseline_60_min_timer,
quantity = 19,
id = 1,
price = 500,
itemType = ItemType.BEITEM
),
onClickUse = { },
onClickCancel = { }
)
}
}
@@ -1,24 +1,36 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box 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.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource 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.unit.dp
import com.github.nacabaro.vbhelper.dtos.ItemDtos import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.github.nacabaro.vbhelper.R import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@Composable @Composable
fun ItemElement( fun ItemElement(
item: ItemDtos.ItemsWithQuantities, itemIcon: Int,
lengthIcon: Int,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (() -> Unit) = { } onClick: (() -> Unit) = { }
) { ) {
@@ -29,7 +41,7 @@ fun ItemElement(
) { ) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
Icon( Icon(
painter = painterResource(id = getIconResource(item.itemIcon)), painter = painterResource(id = itemIcon),
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
.size(96.dp) .size(96.dp)
@@ -37,14 +49,164 @@ fun ItemElement(
.padding(16.dp) .padding(16.dp)
) )
Icon( Icon(
painter = painterResource(id = getLengthResource(item.itemLength)), painter = painterResource(id = lengthIcon),
contentDescription = null, contentDescription = null,
tint = MaterialTheme.colorScheme.surfaceTint, tint = MaterialTheme.colorScheme.surfaceTint,
modifier = Modifier modifier = Modifier
.size(48.dp) .size(48.dp) // Set the size of the overlay image
.align(Alignment.TopStart) .align(Alignment.TopStart) // Align to the top end (top-right corner)
.padding(8.dp) .padding(8.dp)
) )
} }
} }
} }
@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,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
)
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(8.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
)
}
}
@@ -1,40 +0,0 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import com.github.nacabaro.vbhelper.R
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
}
}
@@ -17,9 +17,6 @@ import androidx.compose.ui.Modifier
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ItemsScreen( fun ItemsScreen(
@@ -33,14 +30,14 @@ fun ItemsScreen(
Scaffold( Scaffold(
topBar = { topBar = {
Column { Column {
TopBanner(text = stringResource(R.string.items_title)) TopBanner("Items")
TabRow( TabRow(
selectedTabIndex = selectedTabItem, selectedTabIndex = selectedTabItem,
modifier = Modifier modifier = Modifier
) { ) {
items.forEachIndexed { index, item -> items.forEachIndexed { index, item ->
Tab( Tab(
text = { Text(text = stringResource(item.label)) }, text = { Text(item.label) },
selected = selectedTabItem == index, selected = selectedTabItem == index,
onClick = { selectedTabItem = index } onClick = { selectedTabItem = index }
) )
@@ -11,8 +11,6 @@ import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -54,16 +52,9 @@ class ItemsScreenControllerImpl (
var vbCharacterData: VBCharacterData? = null var vbCharacterData: VBCharacterData? = null
if (characterData.characterType == DeviceType.BEDevice) { if (characterData.characterType == DeviceType.BEDevice) {
beCharacterData = database beCharacterData = database.userCharacterDao().getBeData(characterId)
.userCharacterDao()
.getBeData(characterId)
.firstOrNull()
} else if (characterData.characterType == DeviceType.VBDevice) { } else if (characterData.characterType == DeviceType.VBDevice) {
vbCharacterData = database vbCharacterData = database.userCharacterDao().getVbData(characterId)
.userCharacterDao()
.getVbData(characterId)
.firstOrNull()
} }
if ( if (
@@ -163,34 +154,32 @@ class ItemsScreenControllerImpl (
else -> 0 else -> 0
} }
val specialMissionSlot = when (itemIcon) {
ItemTypes.Step8k.id -> 0
ItemTypes.Step4k.id -> 0
ItemTypes.Vitals1000.id -> 1
ItemTypes.Vitals250.id -> 1
ItemTypes.Battle20.id -> 2
ItemTypes.Battle5.id -> 2
ItemTypes.Win10.id -> 3
ItemTypes.Win4.id -> 3
else -> 0
}
val availableSpecialMissions = database val availableSpecialMissions = database
.userCharacterDao() .userCharacterDao()
.getSpecialMissions(characterId) .getSpecialMissions(characterId)
.first()
var newSpecialMission = availableSpecialMissions[specialMissionSlot] var firstUnavailableMissionSlot: Long = 0
newSpecialMission = SpecialMissions( var watchId = 0
id = newSpecialMission.id,
characterId = newSpecialMission.characterId, for ((index, mission) in availableSpecialMissions.withIndex()) {
if (
mission.status == SpecialMission.Status.UNAVAILABLE
) {
firstUnavailableMissionSlot = mission.id
watchId = index + 1
}
}
val newSpecialMission = SpecialMissions(
id = firstUnavailableMissionSlot,
characterId = characterId,
missionType = specialMissionType,
goal = specialMissionGoal, goal = specialMissionGoal,
watchId = newSpecialMission.watchId,
progress = 0,
status = SpecialMission.Status.AVAILABLE,
timeElapsedInMinutes = 0,
timeLimitInMinutes = itemLength, timeLimitInMinutes = itemLength,
missionType = specialMissionType watchId = watchId,
status = SpecialMission.Status.AVAILABLE,
progress = 0,
timeElapsedInMinutes = 0
) )
database database
@@ -1,37 +1,25 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen package com.github.nacabaro.vbhelper.screens.itemsScreen
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.ItemsRepository import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.flow.first import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ItemsStore( fun ItemsStore(
@@ -39,102 +27,45 @@ fun ItemsStore(
) { ) {
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val itemsRepository = ItemsRepository(application.container.db) val itemsRepository = ItemsRepository(application.container.db)
val myItems by itemsRepository.getAllItems().collectAsState(emptyList()) val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) }
var selectedElementIndex by remember { mutableStateOf<Int?>(null) } var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
val currencyRepository = application.container.currencyRepository LaunchedEffect(itemsRepository) {
val currentCurrency = currencyRepository.currencyValue.collectAsState(0) withContext(Dispatchers.IO) {
myItems.value = itemsRepository.getAllItems()
val scope = rememberCoroutineScope()
if (myItems.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text(stringResource(R.string.items_no_items))
} }
}
if (myItems.value.isEmpty()) {
Text("No items")
} else { } else {
Column() {
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text(
text = stringResource(
R.string.items_store_credits,
currentCurrency.value
),
modifier = Modifier
.padding(8.dp)
)
}
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
modifier = Modifier modifier = Modifier
) { ) {
items(myItems) { index -> items(myItems.value) { index ->
ItemElement( ItemElement(
item = index, itemIcon = getIconResource(index.itemIcon),
lengthIcon = getLengthResource(index.itemLength),
modifier = Modifier modifier = Modifier
.padding(8.dp), .padding(8.dp),
onClick = { onClick = {
selectedElementIndex = myItems.indexOf(index) selectedElementIndex = myItems.value.indexOf(index)
} }
) )
} }
} }
}
}
if (selectedElementIndex != null) { if (selectedElementIndex != null) {
ItemDialog( ItemDialog(
item = myItems[selectedElementIndex!!], name = myItems.value[selectedElementIndex!!].name,
onClickPurchase = { description = myItems.value[selectedElementIndex!!].description,
scope.launch { itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon),
Toast.makeText( lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength),
application.applicationContext, amount = myItems.value[selectedElementIndex!!].quantity,
application.getString( onClickUse = { },
purchaseItem(
application.container.db,
myItems[selectedElementIndex!!],
currencyRepository
)
),
Toast.LENGTH_SHORT
).show()
}
},
onClickCancel = { selectedElementIndex = null } onClickCancel = { selectedElementIndex = null }
) )
} }
} }
suspend fun purchaseItem(
db: AppDatabase,
item: ItemDtos.ItemsWithQuantities,
currencyRepository: CurrencyRepository
): Int {
return if (currencyRepository.currencyValue.first() < item.price) {
R.string.items_not_enough_credits
} else {
db
.itemDao()
.purchaseItem(
item.id,
1
)
currencyRepository
.setCurrencyValue(
currencyRepository.currencyValue.first() - item.price
)
R.string.items_purchase_success
}
} }
@@ -9,7 +9,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -17,14 +17,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.di.VBHelper 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.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.ItemsRepository import com.github.nacabaro.vbhelper.source.ItemsRepository
import com.github.nacabaro.vbhelper.R import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable @Composable
fun MyItems( fun MyItems(
@@ -32,30 +32,36 @@ fun MyItems(
) { ) {
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val itemsRepository = ItemsRepository(application.container.db) val itemsRepository = ItemsRepository(application.container.db)
val myItems by itemsRepository.getUserItems().collectAsState(emptyList()) val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) }
var selectedElementIndex by remember { mutableStateOf<Int?>(null) } var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
if (myItems.isEmpty()) { LaunchedEffect(itemsRepository) {
withContext(Dispatchers.IO) {
myItems.value = itemsRepository.getUserItems()
}
}
if (myItems.value.isEmpty()) {
Column( Column(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Text(stringResource(R.string.items_no_items)) Text("No items")
} }
} else { } else {
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
modifier = Modifier modifier = Modifier
) { ) {
items(myItems) { index -> items(myItems.value) { index ->
ItemElement( ItemElement(
item = index, itemIcon = getIconResource(index.itemIcon),
lengthIcon = getLengthResource(index.itemLength),
modifier = Modifier modifier = Modifier
.padding(8.dp), .padding(8.dp),
onClick = { onClick = {
selectedElementIndex = myItems.indexOf(index) selectedElementIndex = myItems.value.indexOf(index)
} }
) )
} }
@@ -63,7 +69,11 @@ fun MyItems(
if (selectedElementIndex != null) { if (selectedElementIndex != null) {
ItemDialog( ItemDialog(
item = myItems[selectedElementIndex!!], 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 = { onClickUse = {
navController navController
.navigate( .navigate(
@@ -71,7 +81,7 @@ fun MyItems(
.ApplyItem.route .ApplyItem.route
.replace( .replace(
"{itemId}", "{itemId}",
myItems[selectedElementIndex!!].id.toString() myItems.value[selectedElementIndex!!].id.toString()
) )
) )
selectedElementIndex = null selectedElementIndex = null
@@ -19,13 +19,10 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ObtainedItemDialog( fun ObtainedItemDialog(
obtainedItem: ItemDtos.PurchasedItem, obtainedItem: ItemDtos.PurchasedItem,
obtainedCurrency: Int,
onClickDismiss: () -> Unit onClickDismiss: () -> Unit
) { ) {
Dialog( Dialog(
@@ -84,32 +81,17 @@ fun ObtainedItemDialog(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize, fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily, fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource( text = "You have obtained ${obtainedItem.itemAmount} of this item",
R.string.obtained_item_you_have,
obtainedItem.itemAmount
),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 4.dp) .padding(5.dp)
)
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.obtained_item_you_also_got_credits,
obtainedCurrency
),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp)
) )
Button( Button(
onClick = onClickDismiss, onClick = onClickDismiss,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text(text = stringResource(R.string.obtained_item_dismiss)) Text(text = "Dismiss")
} }
} }
} }
@@ -1,80 +0,0 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
navController: NavController
) {
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.scan_title),
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
ScanButton(
text = stringResource(R.string.scan_vb_to_app),
disabled = onClickRead == null,
onClick = onClickRead?: { },
)
Spacer(modifier = Modifier.height(16.dp))
ScanButton(
text = stringResource(R.string.scan_app_to_vb),
disabled = onClickWrite == null,
onClick = onClickWrite?: { },
)
}
}
}
@Composable
fun ScanButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
disabled: Boolean = false,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = !disabled,
) {
Text(
text = text,
fontSize = 16.sp,
modifier = Modifier
.padding(4.dp)
)
}
}
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens package com.github.nacabaro.vbhelper.screens.scanScreen
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -10,22 +10,17 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ActionScreen( fun ReadingCharacterScreen(
topBannerText: String, topBannerText: String,
onClickCancel: () -> Unit, onClickCancel: () -> Unit,
) { ) {
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(topBannerText)
text = topBannerText,
onBackClick = onClickCancel
)
} }
) { innerPadding -> ) { innerPadding ->
Column ( Column (
@@ -35,12 +30,12 @@ fun ActionScreen(
.padding(innerPadding) .padding(innerPadding)
.fillMaxSize() .fillMaxSize()
) { ) {
Text(stringResource(R.string.action_place_near_reader)) Text("Place your Vital Bracelet near the reader...")
Button( Button(
onClick = onClickCancel, onClick = onClickCancel,
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) { ) {
Text(stringResource(R.string.action_cancel)) Text("Cancel")
} }
} }
} }
@@ -1,31 +1,44 @@
package com.github.nacabaro.vbhelper.screens.scanScreen package com.github.nacabaro.vbhelper.screens.scanScreen
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue 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.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.ReadingScreen import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.WritingScreen
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.source.isMissingSecrets import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.github.nacabaro.vbhelper.R
const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER" const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER"
@@ -42,6 +55,8 @@ fun ScanScreen(
val storageRepository = StorageRepository(application.container.db) val storageRepository = StorageRepository(application.container.db)
var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) } var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) }
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
val context = LocalContext.current val context = LocalContext.current
LaunchedEffect(storageRepository) { LaunchedEffect(storageRepository) {
@@ -58,33 +73,143 @@ fun ScanScreen(
} }
} }
var writingScreen by remember { mutableStateOf(false) }
var readingScreen by remember { mutableStateOf(false) } var readingScreen by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var cardSelectScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
if (writingScreen && nfcCharacter != null && characterId != null) { DisposableEffect(readingScreen) {
WritingScreen( if(readingScreen) {
scanScreenController = scanScreenController, scanScreenController.registerActivityLifecycleListener(
nfcCharacter = nfcCharacter!!, SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
characterId = characterId, object: ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = { onComplete = {
writingScreen = false isDoneReadingCharacter = true
navController.navigate(NavigationItems.Home.route)
}, },
onCancel = { onMultipleCards = { cards ->
writingScreen = false cardsRead = cards
navController.navigate(NavigationItems.Home.route) readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
} }
) )
} else if (readingScreen) { }
ReadingScreen( }
scanScreenController = scanScreenController, )
onCancel = { scanScreenController.onClickRead(
readingScreen = false secrets = secrets!!,
navController.navigate(NavigationItems.Home.route)
},
onComplete = { onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
DisposableEffect(writingScreen, isDoneSendingCard) {
if (writingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
}
)
}
if (secrets != null && nfcCharacter != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
onDispose {
if(writingScreen) {
scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false readingScreen = false
navController.navigate(NavigationItems.Home.route) navController.navigate(NavigationItems.Home.route)
} else if (isDoneSendingCard && isDoneWritingCharacter) {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
LaunchedEffect(storageRepository) {
withContext(Dispatchers.IO) {
storageRepository
.deleteCharacter(characterId!!)
}
}
}
if (readingScreen) {
ReadingCharacterScreen("Reading character") {
readingScreen = false
scanScreenController.cancelRead()
}
} else if (writingScreen) {
if (!isDoneSendingCard) {
ReadingCharacterScreen("Sending card") {
writingScreen = false
scanScreenController.cancelRead()
}
} else if (!isDoneWritingCharacter) {
ReadingCharacterScreen("Writing character") {
isDoneSendingCard = false
writingScreen = false
scanScreenController.cancelRead()
}
}
} else if (cardSelectScreen) {
ChooseCard(
cards = cardsRead!!,
onCardSelected = { card ->
cardSelectScreen = false
scanScreenController.flushCharacter(card.id)
} }
) )
} else { } else {
@@ -94,9 +219,9 @@ fun ScanScreen(
else -> { else -> {
{ {
if(secrets == null) { if(secrets == null) {
Toast.makeText(context, context.getString(R.string.scan_secrets_not_initialized), Toast.LENGTH_SHORT).show() Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) { } else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, context.getString(R.string.scan_secrets_not_imported), Toast.LENGTH_SHORT).show() Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
} else { } else {
readingScreen = true // kicks off nfc adapter in DisposableEffect readingScreen = true // kicks off nfc adapter in DisposableEffect
} }
@@ -108,9 +233,9 @@ fun ScanScreen(
else -> { else -> {
{ {
if(secrets == null) { if(secrets == null) {
Toast.makeText(context, context.getString(R.string.scan_secrets_not_initialized), Toast.LENGTH_SHORT).show() Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) { } else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, context.getString(R.string.scan_secrets_not_imported), Toast.LENGTH_SHORT).show() Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
} else { } else {
writingScreen = true // kicks off nfc adapter in DisposableEffect writingScreen = true // kicks off nfc adapter in DisposableEffect
} }
@@ -122,8 +247,66 @@ fun ScanScreen(
} }
} }
@Composable
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
navController: NavController
) {
Scaffold(
topBar = {
TopBanner(
text = "Scan a Vital Bracelet",
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
ScanButton(
text = "Vital Bracelet to App",
disabled = onClickRead == null,
onClick = onClickRead?: { },
)
Spacer(modifier = Modifier.height(16.dp))
ScanButton(
text = "App to Vital Bracelet",
disabled = onClickWrite == null,
onClick = onClickWrite?: { },
)
}
}
}
@Composable
fun ScanButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
disabled: Boolean = false,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = !disabled,
) {
Text(
text = text,
fontSize = 16.sp,
modifier = Modifier
.padding(4.dp)
)
}
}
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun ScanScreenPreview() { fun ScanScreenPreview() {
@@ -6,6 +6,7 @@ import android.nfc.Tag
import android.nfc.tech.NfcA import android.nfc.tech.NfcA
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -24,7 +25,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
class ScanScreenControllerImpl( class ScanScreenControllerImpl(
override val secretsFlow: Flow<Secrets>, override val secretsFlow: Flow<Secrets>,
@@ -38,7 +38,7 @@ class ScanScreenControllerImpl(
init { init {
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity) val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity)
if (maybeNfcAdapter == null) { if (maybeNfcAdapter == null) {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_no_nfc_on_device), Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, "No NFC on device!", Toast.LENGTH_SHORT).show()
} }
nfcAdapter = maybeNfcAdapter nfcAdapter = maybeNfcAdapter
checkSecrets() checkSecrets()
@@ -94,7 +94,7 @@ class ScanScreenControllerImpl(
val nfcData = NfcA.get(tag) val nfcData = NfcA.get(tag)
if (nfcData == null) { if (nfcData == null) {
componentActivity.runOnUiThread { componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_tag_not_vb), Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, "Tag detected is not VB", Toast.LENGTH_SHORT).show()
} }
} }
nfcData.connect() nfcData.connect()
@@ -112,7 +112,7 @@ class ScanScreenControllerImpl(
componentActivity.lifecycleScope.launch(Dispatchers.IO) { componentActivity.lifecycleScope.launch(Dispatchers.IO) {
if(secretsFlow.stateIn(componentActivity.lifecycleScope).value.isMissingSecrets()) { if(secretsFlow.stateIn(componentActivity.lifecycleScope).value.isMissingSecrets()) {
componentActivity.runOnUiThread { componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_missing_secrets), Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, "Missing Secrets. Go to settings and import Vital Arena APK", Toast.LENGTH_SHORT).show()
} }
} }
} }
@@ -126,19 +126,19 @@ class ScanScreenControllerImpl(
handleTag(secrets) { tagCommunicator -> handleTag(secrets) { tagCommunicator ->
try { try {
if (nfcCharacter is VBNfcCharacter) { if (nfcCharacter is VBNfcCharacter) {
AppLogger.d("SendCharacter", "VBNfcCharacter") Log.d("SendCharacter", "VBNfcCharacter")
val castNfcCharacter: VBNfcCharacter = nfcCharacter val castNfcCharacter: VBNfcCharacter = nfcCharacter
tagCommunicator.sendCharacter(castNfcCharacter) tagCommunicator.sendCharacter(castNfcCharacter)
} else if (nfcCharacter is BENfcCharacter) { } else if (nfcCharacter is BENfcCharacter) {
AppLogger.d("SendCharacter", "BENfcCharacter") Log.d("SendCharacter", "BENfcCharacter")
val castNfcCharacter: BENfcCharacter = nfcCharacter val castNfcCharacter: BENfcCharacter = nfcCharacter
tagCommunicator.sendCharacter(castNfcCharacter) tagCommunicator.sendCharacter(castNfcCharacter)
} }
onComplete.invoke() onComplete.invoke()
componentActivity.getString(R.string.scan_sent_character_success) "Sent character successfully!"
} catch (e: Throwable) { } catch (e: Throwable) {
AppLogger.e("TAG", e.stackTraceToString()) Log.e("TAG", e.stackTraceToString())
componentActivity.getString(R.string.scan_error_generic) "Whoops"
} }
} }
} }
@@ -151,13 +151,13 @@ class ScanScreenControllerImpl(
handleTag(secrets) { tagCommunicator -> handleTag(secrets) { tagCommunicator ->
tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId) tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId)
onComplete.invoke() onComplete.invoke()
componentActivity.getString(R.string.scan_sent_dim_success) "Sent DIM successfully!"
} }
} }
// EXTRACTED DIRECTLY FROM EXAMPLE APP // EXTRACTED DIRECTLY FROM EXAMPLE APP
private fun showWirelessSettings() { private fun showWirelessSettings() {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_nfc_must_be_enabled), Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, "NFC must be enabled", Toast.LENGTH_SHORT).show()
componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS)) componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
} }
@@ -177,7 +177,7 @@ class ScanScreenControllerImpl(
) )
val character = nfcGenerator.characterToNfc(characterId) val character = nfcGenerator.characterToNfc(characterId)
AppLogger.d("CharacterType", character.toString()) Log.d("CharacterType", character.toString())
return character return character
} }
@@ -1,11 +1,13 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
@@ -150,13 +152,15 @@ class FromNfcConverter (
nfcCharacter: NfcCharacter, nfcCharacter: NfcCharacter,
cardData: Card cardData: Card
) { ) {
val currentCardProgress = CardProgress(
cardId = cardData.id,
currentStage = nfcCharacter.nextAdventureMissionStage.toInt(),
unlocked = nfcCharacter.nextAdventureMissionStage.toInt() > cardData.stageCount
)
database database
.cardProgressDao() .cardProgressDao()
.updateCardProgress( .updateDimProgress(currentCardProgress)
currentStage = nfcCharacter.nextAdventureMissionStage.toInt(),
cardId = cardData.id,
unlocked = nfcCharacter.nextAdventureMissionStage.toInt() > cardData.stageCount,
)
} }
@@ -248,11 +252,10 @@ class FromNfcConverter (
) { ) {
val vitalsHistoryWatch = nfcCharacter.vitalHistory val vitalsHistoryWatch = nfcCharacter.vitalHistory
val vitalsHistory = vitalsHistoryWatch.map { historyElement -> val vitalsHistory = vitalsHistoryWatch.map { historyElement ->
val year = if (historyElement.year.toInt() !in 2021 .. 2035) 0 else historyElement.year.toInt() Log.d("VitalsHistory", "${historyElement.year.toInt()} ${historyElement.month.toInt()} ${historyElement.day.toInt()}")
VitalsHistory( VitalsHistory(
charId = characterId, charId = characterId,
year = year, year = historyElement.year.toInt(),
month = historyElement.month.toInt(), month = historyElement.month.toInt(),
day = historyElement.day.toInt(), day = historyElement.day.toInt(),
vitalPoints = historyElement.vitalsGained.toInt() vitalPoints = historyElement.vitalsGained.toInt()
@@ -2,6 +2,7 @@ package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.icu.util.Calendar import android.icu.util.Calendar
import android.icu.util.TimeZone import android.icu.util.TimeZone
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.be.FirmwareVersion import com.github.cfogrady.vbnfc.be.FirmwareVersion
@@ -13,7 +14,6 @@ import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.flow.first
import java.util.Date import java.util.Date
class ToNfcConverter( class ToNfcConverter(
@@ -38,10 +38,14 @@ class ToNfcConverter(
.characterDao() .characterDao()
.getCharacterInfo(userCharacter.charId) .getCharacterInfo(userCharacter.charId)
val currentCardStage = database
.cardProgressDao()
.getCardProgress(characterInfo.cardId)
return if (userCharacter.characterType == DeviceType.BEDevice) return if (userCharacter.characterType == DeviceType.BEDevice)
nfcToBENfc(characterId, characterInfo, userCharacter) nfcToBENfc(characterId, characterInfo, currentCardStage, userCharacter)
else else
nfcToVBNfc(characterId, characterInfo, userCharacter) nfcToVBNfc(characterId, characterInfo, currentCardStage, userCharacter)
} }
@@ -49,12 +53,12 @@ class ToNfcConverter(
private suspend fun nfcToVBNfc( private suspend fun nfcToVBNfc(
characterId: Long, characterId: Long,
characterInfo: CharacterDtos.CardCharacterInfo, characterInfo: CharacterDtos.CardCharacterInfo,
currentCardStage: Int,
userCharacter: UserCharacter userCharacter: UserCharacter
): VBNfcCharacter { ): VBNfcCharacter {
val vbData = database val vbData = database
.userCharacterDao() .userCharacterDao()
.getVbData(characterId) .getVbData(characterId)
.first()
val paddedTransformationArray = generateTransformationHistory(characterId, 9) val paddedTransformationArray = generateTransformationHistory(characterId, 9)
@@ -66,7 +70,7 @@ class ToNfcConverter(
stage = characterInfo.stage.toByte(), stage = characterInfo.stage.toByte(),
attribute = characterInfo.attribute, attribute = characterInfo.attribute,
ageInDays = userCharacter.ageInDays.toByte(), ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = characterInfo.currentStage.toByte(), nextAdventureMissionStage = currentCardStage.toByte(),
mood = userCharacter.mood.toByte(), mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(), vitalPoints = userCharacter.vitalPoints.toUShort(),
transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(), transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(),
@@ -97,7 +101,6 @@ class ToNfcConverter(
val cardData = database val cardData = database
.cardDao() .cardDao()
.getCardByCharacterId(userCharacter.id) .getCardByCharacterId(userCharacter.id)
.first()
val appReserved = Array<UShort>(3) { val appReserved = Array<UShort>(3) {
0u 0u
@@ -116,7 +119,6 @@ class ToNfcConverter(
val specialMissions = database val specialMissions = database
.userCharacterDao() .userCharacterDao()
.getSpecialMissions(characterId) .getSpecialMissions(characterId)
.first()
val watchSpecialMissions = specialMissions.map { val watchSpecialMissions = specialMissions.map {
SpecialMission( SpecialMission(
@@ -160,7 +162,7 @@ class ToNfcConverter(
} }
nfcVitalsHistory.map { nfcVitalsHistory.map {
AppLogger.d("NFC", it.toString()) Log.d("NFC", it.toString())
} }
return nfcVitalsHistory return nfcVitalsHistory
@@ -171,12 +173,12 @@ class ToNfcConverter(
private suspend fun nfcToBENfc( private suspend fun nfcToBENfc(
characterId: Long, characterId: Long,
characterInfo: CharacterDtos.CardCharacterInfo, characterInfo: CharacterDtos.CardCharacterInfo,
currentCardStage: Int,
userCharacter: UserCharacter userCharacter: UserCharacter
): BENfcCharacter { ): BENfcCharacter {
val beData = database val beData = database
.userCharacterDao() .userCharacterDao()
.getBeData(characterId) .getBeData(characterId)
.first()
val paddedTransformationArray = generateTransformationHistory(characterId) val paddedTransformationArray = generateTransformationHistory(characterId)
@@ -186,7 +188,7 @@ class ToNfcConverter(
stage = characterInfo.stage.toByte(), stage = characterInfo.stage.toByte(),
attribute = characterInfo.attribute, attribute = characterInfo.attribute,
ageInDays = userCharacter.ageInDays.toByte(), ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = characterInfo.currentStage.toByte(), nextAdventureMissionStage = currentCardStage.toByte(),
mood = userCharacter.mood.toByte(), mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(), vitalPoints = userCharacter.vitalPoints.toUShort(),
itemEffectMentalStateValue = beData.itemEffectMentalStateValue.toByte(), itemEffectMentalStateValue = beData.itemEffectMentalStateValue.toByte(),
@@ -239,14 +241,13 @@ class ToNfcConverter(
): Array<NfcCharacter.Transformation> { ): Array<NfcCharacter.Transformation> {
val transformationHistory = database val transformationHistory = database
.userCharacterDao() .userCharacterDao()
.getTransformationHistory(characterId) .getTransformationHistory(characterId)!!
.first()
.map { .map {
val date = Date(it.transformationDate) val date = Date(it.transformationDate)
val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC")) val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC"))
calendar.time = date calendar.time = date
AppLogger.d( Log.d(
"TransformationHistory", "TransformationHistory",
"Year: ${calendar.get(Calendar.YEAR)}, " + "Year: ${calendar.get(Calendar.YEAR)}, " +
"Month: ${calendar.get(Calendar.MONTH) + 1}, " + "Month: ${calendar.get(Calendar.MONTH) + 1}, " +
@@ -1,61 +0,0 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadCharacterScreen(
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.read_character_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Text(
text = stringResource(R.string.read_character_prepare_device),
textAlign = TextAlign.Center
)
Text(
text = stringResource(R.string.read_character_go_to_connect),
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.padding(8.dp)
)
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.read_character_confirm))
}
}
}
}
@@ -1,112 +0,0 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadingScreen(
scanScreenController: ScanScreenController,
onCancel: () -> Unit,
onComplete: () -> Unit
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
var readingScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var cardSelectScreen by remember { mutableStateOf(false) }
DisposableEffect(readingScreen) {
if(readingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object: ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
}
)
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false
onComplete()
}
if (!readingScreen) {
ReadCharacterScreen(
onClickConfirm = {
readingScreen = true
},
onClickCancel = {
onCancel()
}
)
}
if (readingScreen) {
ActionScreen(topBannerText = stringResource(R.string.reading_character_title),) {
readingScreen = false
scanScreenController.cancelRead()
onCancel()
}
} else if (cardSelectScreen) {
ChooseCard(
cards = cardsRead!!,
onCardSelected = { card ->
cardSelectScreen = false
scanScreenController.flushCharacter(card.id)
}
)
}
}
@@ -1,142 +0,0 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable
fun WriteCardScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.write_card_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(
R.string.write_card_icon_description
),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text(stringResource(R.string.write_card_device_ready))
Text(
stringResource(
R.string.write_card_required_card,
cardDetails.name
)
)
}
}
}
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.write_card_confirm))
}
}
}
}
@@ -1,138 +0,0 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable
fun WriteCharacterScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.write_character_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(R.string.write_character_icon_description),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text(stringResource(R.string.write_character_success))
Text(stringResource(R.string.write_character_wait_ready))
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.write_character_confirm))
}
}
}
}
@@ -1,142 +0,0 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun WritingScreen(
scanScreenController: ScanScreenController,
characterId: Long,
nfcCharacter: NfcCharacter,
onComplete: () -> Unit,
onCancel: () -> Unit,
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
var writing by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var writingConfirmScreen by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
DisposableEffect(writing) {
if (writing) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
)
if (secrets != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
onDispose {
if (writing) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (!writingScreen) {
writing = false
WriteCardScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingScreen = true
}
)
} else if (!isDoneSendingCard) {
writing = true
ActionScreen( topBannerText = stringResource(R.string.sending_card_title)) {
scanScreenController.cancelRead()
onCancel()
}
} else if (!writingConfirmScreen) {
writing = false
WriteCharacterScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingConfirmScreen = true
}
)
} else if (!isDoneWritingCharacter) {
writing = true
ActionScreen(topBannerText = stringResource(R.string.writing_character_action_title)) {
isDoneSendingCard = false
scanScreenController.cancelRead()
onCancel()
}
}
var completedWriting by remember { mutableStateOf(false) }
LaunchedEffect(isDoneSendingCard, isDoneWritingCharacter) {
withContext(Dispatchers.IO) {
if (isDoneSendingCard && isDoneWritingCharacter) {
storageRepository
.deleteCharacter(characterId)
completedWriting = true
}
}
}
if (completedWriting) {
onComplete()
}
}
@@ -6,10 +6,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R import com.github.nacabaro.vbhelper.navigation.NavigationItems
@Composable @Composable
fun CreditsScreen( fun CreditsScreen(
@@ -18,9 +17,12 @@ fun CreditsScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.credits_title), text = "Credits",
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
},
onGearClick = {
navController.navigate(NavigationItems.Viewer.route)
} }
) )
}, },
@@ -31,13 +33,13 @@ fun CreditsScreen(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
SettingsSection(stringResource(R.string.credits_section_reverse_engineering)) SettingsSection("Reverse engineering")
SettingsEntry(title = "cyanic", description = stringResource(R.string.credits_cyanic_description)) { } SettingsEntry(title = "cyanic", description = "Reversed the firmware and helped us during development.") { }
SettingsSection(stringResource(R.string.credits_section_app_development)) SettingsSection("Application development")
SettingsEntry(title = "cfogrady", description = stringResource(R.string.credits_cfogrady_description)) { } SettingsEntry(title = "cfogrady", description = "Developed vb-lib-nfc and part of this application.") { }
SettingsEntry(title = "nacabaro", description = stringResource(R.string.credits_nacabaro_description)) { } SettingsEntry(title = "nacabaro", description = "Developed this application.") { }
SettingsEntry(title = "lightheel", description = stringResource(R.string.credits_lightheel_description)) { } SettingsEntry(title = "lightheel", description = "Developing the battling part for this application, including server. Still in the works.") { }
SettingsEntry(title = "shvstrz", description = stringResource(R.string.credits_shvstrz_description)) { } SettingsEntry(title = "shvstrz", description = "Designing the app icon in SVG.") { }
} }
} }
} }
@@ -1,44 +0,0 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.TopBanner
@Composable
fun LoggerScreen(
navController: NavController
) {
Scaffold (
topBar = {
TopBanner(
text = stringResource(R.string.logs_title),
onBackClick = {
navController.popBackStack()
}
)
},
modifier = Modifier
.fillMaxSize()
) { contentPadding ->
Column (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
Text(
text = AppLogger.getLogs(),
modifier = Modifier
.verticalScroll(rememberScrollState())
)
}
}
}
@@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.content.Intent
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -14,29 +13,22 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.R
import androidx.core.net.toUri
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
navController: NavController, navController: NavController,
settingsScreenController: SettingsScreenControllerImpl, settingsScreenController: SettingsScreenControllerImpl,
) { ) {
val context = LocalContext.current
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.settings_title), text = "Settings",
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@@ -51,57 +43,25 @@ fun SettingsScreen(
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
SettingsSection(title = stringResource(R.string.settings_section_nfc)) SettingsSection("NFC Communication")
SettingsEntry( SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
title = stringResource(R.string.settings_import_apk_title),
description = stringResource(R.string.settings_import_apk_desc)
) {
settingsScreenController.onClickImportApk() settingsScreenController.onClickImportApk()
} }
SettingsSection("DiM/BEm management")
SettingsSection(title = stringResource(R.string.settings_section_dim_bem)) SettingsEntry(title = "Import card", description = "Import DiM/BEm card file") {
SettingsEntry(
title = stringResource(R.string.settings_import_card_title),
description = stringResource(R.string.settings_import_card_desc)
) {
settingsScreenController.onClickImportCard() settingsScreenController.onClickImportCard()
} }
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
SettingsSection(title = stringResource(R.string.settings_section_about)) SettingsSection("About and credits")
SettingsEntry( SettingsEntry(title = "Credits", description = "Credits") {
title = stringResource(R.string.settings_credits_title),
description = stringResource(R.string.settings_credits_desc)
) {
navController.navigate(NavigationItems.Credits.route) navController.navigate(NavigationItems.Credits.route)
} }
SettingsEntry( SettingsEntry(title = "About", description = "About") { }
title = stringResource(R.string.settings_about_title), SettingsSection("Data management")
description = stringResource(R.string.settings_about_desc) SettingsEntry(title = "Export data", description = "Export application database") {
) {
val browserIntent = Intent(
Intent.ACTION_VIEW,
"https://github.com/nacabaro/vbhelper/".toUri()
)
context.startActivity(browserIntent)
}
SettingsEntry(
title = stringResource(R.string.settings_logs_title),
description = stringResource(R.string.settings_logs_desc)
) {
navController.navigate(NavigationItems.Logger.route)
}
SettingsSection(title = stringResource(R.string.settings_section_data))
SettingsEntry(
title = stringResource(R.string.settings_export_data_title),
description = stringResource(R.string.settings_export_data_desc)
) {
settingsScreenController.onClickOpenDirectory() settingsScreenController.onClickOpenDirectory()
} }
SettingsEntry( SettingsEntry(title = "Import data", description = "Import application database") {
title = stringResource(R.string.settings_import_data_title),
description = stringResource(R.string.settings_import_data_desc)
) {
settingsScreenController.onClickImportDatabase() settingsScreenController.onClickImportDatabase()
} }
} }
@@ -135,7 +95,7 @@ fun SettingsSection(
) { ) {
Box ( Box (
modifier = Modifier modifier = Modifier
.padding(start = 16.dp, top = 16.dp, bottom = 4.dp) .padding(start = 16.dp)
) { ) {
Text( Text(
text = title, text = title,
@@ -3,24 +3,38 @@ package com.github.nacabaro.vbhelper.screens.settingsScreen
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import android.net.Uri import android.net.Uri
import android.provider.OpenableColumns
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import com.github.cfogrady.vb.dim.card.BemCard
import com.github.cfogrady.vb.dim.card.DimCard
import com.github.cfogrady.vb.dim.card.DimReader
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.screens.settingsScreen.controllers.CardImportController import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.screens.settingsScreen.controllers.DatabaseManagementController import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardBackground
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.card.CardSprites
import com.github.nacabaro.vbhelper.source.ApkSecretsImporter import com.github.nacabaro.vbhelper.source.ApkSecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsImporter import com.github.nacabaro.vbhelper.source.SecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsRepository import com.github.nacabaro.vbhelper.source.SecretsRepository
import com.github.nacabaro.vbhelper.source.proto.Secrets import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import java.io.File
import java.io.InputStream
import java.io.OutputStream
class SettingsScreenControllerImpl( class SettingsScreenControllerImpl(
private val context: ComponentActivity, private val context: ComponentActivity,
): SettingsScreenController { ): SettingsScreenController {
private val roomDbName = "internalDb"
private val filePickerLauncher: ActivityResultLauncher<String> private val filePickerLauncher: ActivityResultLauncher<String>
private val filePickerOpenerLauncher: ActivityResultLauncher<Array<String>> private val filePickerOpenerLauncher: ActivityResultLauncher<Array<String>>
private val filePickerApk: ActivityResultLauncher<Array<String>> private val filePickerApk: ActivityResultLauncher<Array<String>>
@@ -29,17 +43,13 @@ class SettingsScreenControllerImpl(
private val application = context.applicationContext as VBHelper private val application = context.applicationContext as VBHelper
private val secretsRepository: SecretsRepository = application.container.dataStoreSecretsRepository private val secretsRepository: SecretsRepository = application.container.dataStoreSecretsRepository
private val database: AppDatabase = application.container.db private val database: AppDatabase = application.container.db
private val databaseManagementController = DatabaseManagementController(
componentActivity = context,
application = application
)
init { init {
filePickerLauncher = context.registerForActivityResult( filePickerLauncher = context.registerForActivityResult(
ActivityResultContracts.CreateDocument("application/octet-stream") ActivityResultContracts.CreateDocument("application/octet-stream")
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
databaseManagementController.exportDatabase(uri) exportDatabase(uri)
} else { } else {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT) Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT)
@@ -52,7 +62,7 @@ class SettingsScreenControllerImpl(
ActivityResultContracts.OpenDocument() ActivityResultContracts.OpenDocument()
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
databaseManagementController.importDatabase(uri) importDatabase(uri)
} else { } else {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show() Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show()
@@ -101,14 +111,324 @@ class SettingsScreenControllerImpl(
filePickerCard.launch(arrayOf("*/*")) filePickerCard.launch(arrayOf("*/*"))
} }
private suspend fun importEvoData(
cardId: Long,
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
) {
for (index in 0 until card.transformationRequirements.transformationEntries.size) {
val evo = card.transformationRequirements.transformationEntries[index]
var transformationTimerHours: Int
var unlockAdventureLevel: Int
if (card is BemCard) {
transformationTimerHours = card
.transformationRequirements
.transformationEntries[index]
.minutesUntilTransformation / 60
unlockAdventureLevel = if (
card
.transformationRequirements
.transformationEntries[index]
.requiredCompletedAdventureLevel == 65535
) {
0
} else {
card
.transformationRequirements
.transformationEntries[index]
.requiredCompletedAdventureLevel
}
} else {
transformationTimerHours = (card as DimCard)
.transformationRequirements
.transformationEntries[index]
.hoursUntilEvolution
unlockAdventureLevel = if (
card
.adventureLevels
.levels
.last()
.bossCharacterIndex == card.transformationRequirements.transformationEntries[index].toCharacterIndex
) {
14
/*
Magic number incoming!!
In the case of DiMCards, stage 15 is the one that unlocks the locked character.
We know it is a locked character if the last adventure level's boss character index
is the current index. If it is, we add stage 15 complete as a requirement for transformation.
*/
} else {
0
/*
Another magic number...
The rest of the characters are not locked.
*/
}
}
database
.characterDao()
.insertPossibleTransformation(
cardId = cardId,
fromChraraIndex = evo.fromCharacterIndex,
toChraraIndex = evo.toCharacterIndex,
requiredVitals = evo.requiredVitalValues,
requiredTrophies = evo.requiredTrophies,
requiredBattles = evo.requiredBattles,
requiredWinRate = evo.requiredWinRatio,
requiredAdventureLevelCompleted = unlockAdventureLevel,
changeTimerHours = transformationTimerHours
)
}
}
private suspend fun importCharacterData(
cardId: Long,
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
) {
var spriteCounter = when (card is BemCard) {
true -> 54
false -> 10
}
val domainCharacters = mutableListOf<CardCharacter>()
val characters = card
.characterStats
.characterEntries
for (index in 0 until characters.size) {
var domainSprite: Sprite?
if (index < 2 && card is DimCard) {
domainSprite = Sprite(
width = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.width,
height = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.height,
spriteIdle1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteIdle2 = card.spriteData.sprites[spriteCounter + 2].pixelData,
spriteWalk1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteWalk2 = card.spriteData.sprites[spriteCounter + 3].pixelData,
spriteRun1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteRun2 = card.spriteData.sprites[spriteCounter + 3].pixelData,
spriteTrain1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteTrain2 = card.spriteData.sprites[spriteCounter + 3].pixelData,
spriteHappy = card.spriteData.sprites[spriteCounter + 4].pixelData,
spriteSleep = card.spriteData.sprites[spriteCounter + 5].pixelData,
spriteAttack = card.spriteData.sprites[spriteCounter + 2].pixelData,
spriteDodge = card.spriteData.sprites[spriteCounter + 3].pixelData,
)
} else {
domainSprite = Sprite(
width = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.width,
height = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.height,
spriteIdle1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteIdle2 = card.spriteData.sprites[spriteCounter + 2].pixelData,
spriteWalk1 = card.spriteData.sprites[spriteCounter + 3].pixelData,
spriteWalk2 = card.spriteData.sprites[spriteCounter + 4].pixelData,
spriteRun1 = card.spriteData.sprites[spriteCounter + 5].pixelData,
spriteRun2 = card.spriteData.sprites[spriteCounter + 6].pixelData,
spriteTrain1 = card.spriteData.sprites[spriteCounter + 7].pixelData,
spriteTrain2 = card.spriteData.sprites[spriteCounter + 8].pixelData,
spriteHappy = card.spriteData.sprites[spriteCounter + 9].pixelData,
spriteSleep = card.spriteData.sprites[spriteCounter + 10].pixelData,
spriteAttack = card.spriteData.sprites[spriteCounter + 11].pixelData,
spriteDodge = card.spriteData.sprites[spriteCounter + 12].pixelData
)
}
val spriteId = database
.spriteDao()
.insertSprite(domainSprite)
domainCharacters.add(
CardCharacter(
cardId = cardId,
spriteId = spriteId,
charaIndex = index,
nameSprite = card.spriteData.sprites[spriteCounter].pixelData,
stage = characters[index].stage,
attribute = NfcCharacter.Attribute.entries[characters[index].attribute],
baseHp = characters[index].hp,
baseBp = characters[index].dp,
baseAp = characters[index].ap,
nameWidth = card.spriteData.sprites[spriteCounter].spriteDimensions.width,
nameHeight = card.spriteData.sprites[spriteCounter].spriteDimensions.height
)
)
spriteCounter += if (card is BemCard) {
14
} else {
when (index) {
0 -> 6
1 -> 7
else -> 14
}
}
}
database
.characterDao()
.insertCharacter(*domainCharacters.toTypedArray())
}
private suspend fun importAdventureMissions(
cardId: Long,
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
) {
Log.d("importAdventureMissions", "Importing adventure missions")
if (card is BemCard) {
card.adventureLevels.levels.forEach {
database
.cardAdventureDao()
.insertNewAdventure(
cardId = cardId,
characterId = it.bossCharacterIndex,
steps = it.steps,
bossAp = it.bossAp,
bossHp = it.bossHp,
bossDp = it.bossDp,
bossBp = it.bossBp
)
}
} else if (card is DimCard) {
card.adventureLevels.levels.map {
database
.cardAdventureDao()
.insertNewAdventure(
cardId = cardId,
characterId = it.bossCharacterIndex,
steps = it.steps,
bossAp = it.bossAp,
bossHp = it.bossHp,
bossDp = it.bossDp,
bossBp = null
)
}
}
}
private suspend fun importCardFusions(
cardId: Long,
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
) {
Log.d("importCardFusions", "Importing card fusions")
if (card is DimCard) {
card
.attributeFusions
.entries
.forEach {
database
.cardFusionsDao()
.insertNewFusion(
cardId = cardId,
fromCharaId = it.characterIndex,
toCharaIdVirus = it.attribute1Fusion,
toCharaIdData = it.attribute2Fusion,
toCharaIdVaccine = it.attribute3Fusion,
toCharaIdFree = it.attribute4Fusion
)
}
}
}
private fun updateCardProgress(
cardId: Long,
) {
val cardProgress = CardProgress(
cardId = cardId,
currentStage = 0,
unlocked = false
)
database
.cardProgressDao()
.updateDimProgress(cardProgress)
}
private suspend fun importBackgrounds(
cardId: Long,
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
) {
val backgrounds = mutableListOf<CardBackground>()
if (card is DimCard) {
backgrounds.add(
CardBackground(
cardId = cardId,
background = card.spriteData.sprites[1].pixelData,
backgroundWidth = card.spriteData.sprites[1].width,
backgroundHeight = card.spriteData.sprites[1].height
)
)
} else if (card is BemCard) {
val possibleBackgroundsIds = listOf(1, 10, 30, 31, 32, 33)
for (id in possibleBackgroundsIds) {
backgrounds.add(
CardBackground(
cardId = cardId,
background = card.spriteData.sprites[id].pixelData,
backgroundWidth = card.spriteData.sprites[id].width,
backgroundHeight = card.spriteData.sprites[id].height
)
)
}
}
database
.cardBackgroundDao()
.insertCardBackground(*backgrounds.toTypedArray())
}
private fun importCard(uri: Uri) { private fun importCard(uri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) { context.lifecycleScope.launch(Dispatchers.IO) {
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
val inputStream = contentResolver.openInputStream(uri) val inputStream = contentResolver.openInputStream(uri)
inputStream.use { fileReader -> inputStream.use { fileReader ->
val cardImportController = CardImportController(database) val dimReader = DimReader()
cardImportController.importCard(fileReader) val card = dimReader.readCard(fileReader, false)
val cardModel = Card(
cardId = card.header.dimId,
logo = card.spriteData.sprites[0].pixelData,
name = card.spriteData.text,
stageCount = card.adventureLevels.levels.size,
logoHeight = card.spriteData.sprites[0].height,
logoWidth = card.spriteData.sprites[0].width,
isBEm = card is BemCard
)
val cardId = database
.cardDao()
.insertNewCard(cardModel)
updateCardProgress(cardId = cardId)
importCharacterData(cardId, card)
importEvoData(cardId, card)
importAdventureMissions(cardId, card)
importCardFusions(cardId, card)
importBackgrounds(cardId, card)
val cardSprites: List<CardSprites> = card.spriteData.sprites.map {
CardSprites(
sprite = it.pixelData,
width = it.width,
height = it.height
)
}
database
.cardDao()
.insertSprites(*cardSprites.toTypedArray())
} }
inputStream?.close() inputStream?.close()
@@ -118,6 +438,100 @@ class SettingsScreenControllerImpl(
} }
} }
private fun exportDatabase(destinationUri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) {
try {
val dbFile = File(context.getDatabasePath(roomDbName).absolutePath)
if (!dbFile.exists()) {
throw IllegalStateException("Database file does not exist!")
}
application.container.db.close()
context.contentResolver.openOutputStream(destinationUri)?.use { outputStream ->
dbFile.inputStream().use { inputStream ->
copyFile(inputStream, outputStream)
}
} ?: throw IllegalArgumentException("Unable to open destination Uri for writing")
context.runOnUiThread {
Toast.makeText(context, "Database exported successfully!", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Closing application to avoid changes.", Toast.LENGTH_LONG).show()
context.finishAffinity()
}
} catch (e: Exception) {
Log.e("ScanScreenController", "Error exporting database $e")
context.runOnUiThread {
Toast.makeText(context, "Error exporting database: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
private fun importDatabase(sourceUri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) {
try {
if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) {
context.runOnUiThread {
Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show()
}
return@launch
}
application.container.db.close()
val dbPath = context.getDatabasePath(roomDbName)
val shmFile = File(dbPath.parent, "$roomDbName-shm")
val walFile = File(dbPath.parent, "$roomDbName-wal")
// Delete existing database files
if (dbPath.exists()) dbPath.delete()
if (shmFile.exists()) shmFile.delete()
if (walFile.exists()) walFile.delete()
val dbFile = File(dbPath.absolutePath)
context.contentResolver.openInputStream(sourceUri)?.use { inputStream ->
dbFile.outputStream().use { outputStream ->
copyFile(inputStream, outputStream)
}
} ?: throw IllegalArgumentException("Unable to open source Uri for reading")
context.runOnUiThread {
Toast.makeText(context, "Database imported successfully!", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Reopen the app to finish import process!", Toast.LENGTH_LONG).show()
context.finishAffinity()
}
} catch (e: Exception) {
Log.e("ScanScreenController", "Error importing database $e")
context.runOnUiThread {
Toast.makeText(context, "Error importing database: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
private fun getFileNameFromUri(uri: Uri): String? {
var fileName: String? = null
val cursor = context.contentResolver.query(uri, null, null, null, null)
cursor?.use {
if (it.moveToFirst()) {
val nameIndex = it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
fileName = it.getString(nameIndex)
}
}
return fileName
}
private fun copyFile(inputStream: InputStream, outputStream: OutputStream) {
val buffer = ByteArray(1024)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
outputStream.flush()
}
private fun importApk(uri: Uri) { private fun importApk(uri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) { context.lifecycleScope.launch(Dispatchers.IO) {
context.contentResolver.openInputStream(uri).use { context.contentResolver.openInputStream(uri).use {
@@ -1,319 +0,0 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen.controllers
import com.github.cfogrady.vb.dim.card.BemCard
import com.github.cfogrady.vb.dim.card.DimCard
import com.github.cfogrady.vb.dim.card.DimReader
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import java.io.InputStream
class CardImportController(
private val database: AppDatabase
) {
suspend fun importCard(
fileReader: InputStream?
) {
val dimReader = DimReader()
val card = dimReader.readCard(fileReader, false)
val cardModel = Card(
cardId = card.header.dimId,
logo = card.spriteData.sprites[0].pixelData,
name = card.spriteData.text,
stageCount = card.adventureLevels.levels.size,
logoHeight = card.spriteData.sprites[0].height,
logoWidth = card.spriteData.sprites[0].width,
isBEm = card is BemCard
)
val cardId = database
.cardDao()
.insertNewCard(cardModel)
updateCardProgress(cardId = cardId)
importCharacterData(cardId, card)
importEvoData(cardId, card)
importAdventureMissions(cardId, card)
importCardFusions(cardId, card)
}
private fun updateCardProgress(
cardId: Long,
) {
database
.cardProgressDao()
.insertCardProgress(
CardProgress(
cardId = cardId,
currentStage = 1,
unlocked = false
)
)
}
private suspend fun importCharacterData(
cardId: Long,
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
) {
var spriteCounter = when (card is BemCard) {
true -> 54
false -> 10
}
val domainCharacters = mutableListOf<CardCharacter>()
val characters = card
.characterStats
.characterEntries
for (index in 0 until characters.size) {
var domainSprite: Sprite?
if (index < 2 && card is DimCard) {
domainSprite = Sprite(
width = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.width,
height = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.height,
spriteIdle1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteIdle2 = card.spriteData.sprites[spriteCounter + 2].pixelData,
spriteWalk1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteWalk2 = card.spriteData.sprites[spriteCounter + 3].pixelData,
spriteRun1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteRun2 = card.spriteData.sprites[spriteCounter + 3].pixelData,
spriteTrain1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteTrain2 = card.spriteData.sprites[spriteCounter + 3].pixelData,
spriteHappy = card.spriteData.sprites[spriteCounter + 4].pixelData,
spriteSleep = card.spriteData.sprites[spriteCounter + 5].pixelData,
spriteAttack = card.spriteData.sprites[spriteCounter + 2].pixelData,
spriteDodge = card.spriteData.sprites[spriteCounter + 3].pixelData
)
} else {
domainSprite = Sprite(
width = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.width,
height = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.height,
spriteIdle1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
spriteIdle2 = card.spriteData.sprites[spriteCounter + 2].pixelData,
spriteWalk1 = card.spriteData.sprites[spriteCounter + 3].pixelData,
spriteWalk2 = card.spriteData.sprites[spriteCounter + 4].pixelData,
spriteRun1 = card.spriteData.sprites[spriteCounter + 5].pixelData,
spriteRun2 = card.spriteData.sprites[spriteCounter + 6].pixelData,
spriteTrain1 = card.spriteData.sprites[spriteCounter + 7].pixelData,
spriteTrain2 = card.spriteData.sprites[spriteCounter + 8].pixelData,
spriteHappy = card.spriteData.sprites[spriteCounter + 9].pixelData,
spriteSleep = card.spriteData.sprites[spriteCounter + 10].pixelData,
spriteAttack = card.spriteData.sprites[spriteCounter + 11].pixelData,
spriteDodge = card.spriteData.sprites[spriteCounter + 12].pixelData
)
}
val spriteId = database
.spriteDao()
.insertSprite(domainSprite)
domainCharacters.add(
CardCharacter(
cardId = cardId,
spriteId = spriteId,
charaIndex = index,
nameSprite = card.spriteData.sprites[spriteCounter].pixelData,
stage = characters[index].stage,
attribute = NfcCharacter.Attribute.entries[characters[index].attribute],
baseHp = characters[index].hp,
baseBp = characters[index].dp,
baseAp = characters[index].ap,
nameWidth = card.spriteData.sprites[spriteCounter].spriteDimensions.width,
nameHeight = card.spriteData.sprites[spriteCounter].spriteDimensions.height
)
)
spriteCounter += if (card is BemCard) {
14
} else {
when (index) {
0 -> 6
1 -> 7
else -> 14
}
}
}
database
.characterDao()
.insertCharacter(*domainCharacters.toTypedArray())
}
private suspend fun importAdventureMissions(
cardId: Long,
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
) {
AppLogger.d("importAdventureMissions", "Importing adventure missions")
if (card is BemCard) {
card.adventureLevels.levels.forEach {
database
.cardAdventureDao()
.insertNewAdventure(
cardId = cardId,
characterId = it.bossCharacterIndex,
steps = it.steps,
bossAp = it.bossAp,
bossHp = it.bossHp,
bossDp = it.bossDp,
bossBp = it.bossBp
)
}
} else if (card is DimCard) {
card.adventureLevels.levels.map {
database
.cardAdventureDao()
.insertNewAdventure(
cardId = cardId,
characterId = it.bossCharacterIndex,
steps = it.steps,
bossAp = it.bossAp,
bossHp = it.bossHp,
bossDp = it.bossDp,
bossBp = null
)
}
}
}
private suspend fun importCardFusions(
cardId: Long,
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
) {
AppLogger.d("importCardFusions", "Importing card fusions")
if (card is DimCard) {
card
.attributeFusions
.entries
.forEach {
AppLogger.d("importCardFusions", "Importing fusion: ${it.attribute1Fusion}")
if (it.attribute1Fusion != 65535 && it.characterIndex != 65535) {
database
.cardFusionsDao()
.insertNewFusion(
cardId = cardId,
fromCharaId = it.characterIndex,
attribute = NfcCharacter.Attribute.Virus,
toCharaId = it.attribute1Fusion,
)
}
if (it.attribute2Fusion != 65535 && it.characterIndex != 65535) {
database
.cardFusionsDao()
.insertNewFusion(
cardId = cardId,
fromCharaId = it.characterIndex,
attribute = NfcCharacter.Attribute.Data,
toCharaId = it.attribute2Fusion,
)
}
if (it.attribute3Fusion != 65535 && it.characterIndex != 65535) {
database
.cardFusionsDao()
.insertNewFusion(
cardId = cardId,
fromCharaId = it.characterIndex,
attribute = NfcCharacter.Attribute.Vaccine,
toCharaId = it.attribute3Fusion,
)
}
if (it.attribute4Fusion != 65535 && it.characterIndex != 65535) {
database
.cardFusionsDao()
.insertNewFusion(
cardId = cardId,
fromCharaId = it.characterIndex,
attribute = NfcCharacter.Attribute.Free,
toCharaId = it.attribute4Fusion,
)
}
}
}
}
private suspend fun importEvoData(
cardId: Long,
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
) {
for (index in 0 until card.transformationRequirements.transformationEntries.size) {
val evo = card.transformationRequirements.transformationEntries[index]
var transformationTimerHours: Int
var unlockAdventureLevel: Int
if (card is BemCard) {
transformationTimerHours = card
.transformationRequirements
.transformationEntries[index]
.minutesUntilTransformation / 60
unlockAdventureLevel = if (
card
.transformationRequirements
.transformationEntries[index]
.requiredCompletedAdventureLevel == 65535
) {
0
} else {
card
.transformationRequirements
.transformationEntries[index]
.requiredCompletedAdventureLevel
}
} else {
transformationTimerHours = (card as DimCard)
.transformationRequirements
.transformationEntries[index]
.hoursUntilEvolution
unlockAdventureLevel = if (
card
.adventureLevels
.levels
.last()
.bossCharacterIndex == card.transformationRequirements.transformationEntries[index].toCharacterIndex
) {
14
/*
Magic number incoming!!
In the case of DiMCards, stage 15 is the one that unlocks the locked character.
We know it is a locked character if the last adventure level's boss character index
is the current index. If it is, we add stage 15 complete as a requirement for transformation.
*/
} else {
0
/*
Another magic number...
The rest of the characters are not locked.
*/
}
}
database
.characterDao()
.insertPossibleTransformation(
cardId = cardId,
fromChraraIndex = evo.fromCharacterIndex,
toChraraIndex = evo.toCharacterIndex,
requiredVitals = evo.requiredVitalValues,
requiredTrophies = evo.requiredTrophies,
requiredBattles = evo.requiredBattles,
requiredWinRate = evo.requiredWinRatio,
requiredAdventureLevelCompleted = unlockAdventureLevel,
changeTimerHours = transformationTimerHours
)
}
}
}
@@ -1,114 +0,0 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen.controllers
import android.net.Uri
import android.provider.OpenableColumns
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.io.InputStream
import java.io.OutputStream
class DatabaseManagementController(
val componentActivity: ComponentActivity,
val application: VBHelper
) {
private val roomDbName = "internalDb"
fun exportDatabase( destinationUri: Uri) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
try {
val dbFile = File(componentActivity.getDatabasePath(roomDbName).absolutePath)
if (!dbFile.exists()) {
throw IllegalStateException("Database file does not exist!")
}
application.container.db.close()
componentActivity.contentResolver.openOutputStream(destinationUri)?.use { outputStream ->
dbFile.inputStream().use { inputStream ->
copyFile(inputStream, outputStream)
}
} ?: throw IllegalArgumentException("Unable to open destination Uri for writing")
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Database exported successfully!", Toast.LENGTH_SHORT).show()
Toast.makeText(componentActivity, "Closing application to avoid changes.", Toast.LENGTH_LONG).show()
componentActivity.finishAffinity()
}
} catch (e: Exception) {
AppLogger.e("ScanScreenController", "Error exporting database $e")
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Error exporting database: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
fun importDatabase(sourceUri: Uri) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
try {
if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Invalid file format", Toast.LENGTH_SHORT).show()
}
return@launch
}
application.container.db.close()
val dbPath = componentActivity.getDatabasePath(roomDbName)
val shmFile = File(dbPath.parent, "$roomDbName-shm")
val walFile = File(dbPath.parent, "$roomDbName-wal")
// Delete existing database files
if (dbPath.exists()) dbPath.delete()
if (shmFile.exists()) shmFile.delete()
if (walFile.exists()) walFile.delete()
val dbFile = File(dbPath.absolutePath)
componentActivity.contentResolver.openInputStream(sourceUri)?.use { inputStream ->
dbFile.outputStream().use { outputStream ->
copyFile(inputStream, outputStream)
}
} ?: throw IllegalArgumentException("Unable to open source Uri for reading")
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Database imported successfully!", Toast.LENGTH_SHORT).show()
Toast.makeText(componentActivity, "Reopen the app to finish import process!", Toast.LENGTH_LONG).show()
componentActivity.finishAffinity()
}
} catch (e: Exception) {
AppLogger.e("ScanScreenController", "Error importing database $e")
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Error importing database: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
private fun copyFile(inputStream: InputStream, outputStream: OutputStream) {
val buffer = ByteArray(1024)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
outputStream.flush()
}
private fun getFileNameFromUri(uri: Uri): String? {
var fileName: String? = null
val cursor = componentActivity.contentResolver.query(uri, null, null, null, null)
cursor?.use {
if (it.moveToFirst()) {
val nameIndex = it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
fileName = it.getString(nameIndex)
}
}
return fileName
}
}
@@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.screens.spriteViewer package com.github.nacabaro.vbhelper.screens.spriteViewer
import android.graphics.Bitmap import android.graphics.Bitmap
import android.util.Log
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -12,6 +13,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -25,6 +27,8 @@ fun SpriteViewer(
) { ) {
val spriteList = remember { mutableStateListOf<Bitmap>() } val spriteList = remember { mutableStateListOf<Bitmap>() }
Log.d("SpriteViewer", "spriteList: $spriteList")
LaunchedEffect(spriteViewerController) { LaunchedEffect(spriteViewerController) {
val sprites = spriteViewerController.getAllSprites() val sprites = spriteViewerController.getAllSprites()
val bitmapData = spriteViewerController.convertToBitmap(sprites) val bitmapData = spriteViewerController.convertToBitmap(sprites)
@@ -1,9 +1,9 @@
package com.github.nacabaro.vbhelper.screens.spriteViewer package com.github.nacabaro.vbhelper.screens.spriteViewer
import android.graphics.Bitmap import android.graphics.Bitmap
import com.github.nacabaro.vbhelper.domain.characters.Sprite import com.github.nacabaro.vbhelper.domain.card.CardBackground
interface SpriteViewerController { interface SpriteViewerController {
suspend fun getAllSprites(): List<Sprite> suspend fun getAllSprites(): List<CardBackground>
fun convertToBitmap(sprites: List<Sprite>): List<Bitmap> fun convertToBitmap(sprites: List<CardBackground>): List<Bitmap>
} }
@@ -3,60 +3,27 @@ package com.github.nacabaro.vbhelper.screens.spriteViewer
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import java.nio.ByteBuffer import java.nio.ByteBuffer
import androidx.core.graphics.createBitmap import androidx.core.graphics.createBitmap
import com.github.nacabaro.vbhelper.domain.card.CardBackground
class SpriteViewerControllerImpl( class SpriteViewerControllerImpl(
private val context: ComponentActivity private val context: ComponentActivity
) : SpriteViewerController { ) : SpriteViewerController {
override suspend fun getAllSprites(): List<Sprite> { override suspend fun getAllSprites(): List<CardBackground> {
val applicationContext = context.applicationContext as VBHelper val applicationContext = context.applicationContext as VBHelper
val db = applicationContext.container.db val db = applicationContext.container.db
val sprites = db.spriteDao().getAllSprites() val sprites = db.cardBackgroundDao().getBackgrounds()
return sprites return sprites
} }
// I don't like this, chief // I don't like this, chief
override fun convertToBitmap(sprites: List<Sprite>): List<Bitmap> { override fun convertToBitmap(sprites: List<CardBackground>): List<Bitmap> {
val bitmapList = mutableListOf<Bitmap>() val bitmapList = mutableListOf<Bitmap>()
for (sprite in sprites) { for (sprite in sprites) {
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply { bitmapList.add(createBitmap(sprite.backgroundWidth, sprite.backgroundHeight, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle1)) copyPixelsFromBuffer(ByteBuffer.wrap(sprite.background))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle2))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk1))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk2))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun1))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun2))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain1))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain2))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteHappy))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteSleep))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteAttack))
})
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteDodge))
}) })
} }
@@ -33,15 +33,11 @@ import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap import com.github.nacabaro.vbhelper.utils.getBitmap
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun StorageDialog( fun StorageDialog(
characterId: Long, characterId: Long,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onClickDelete: () -> Unit,
onSendToBracelet: () -> Unit, onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit, onClickSetActive: () -> Unit,
onClickSendToAdventure: (time: Long) -> Unit onClickSendToAdventure: (time: Long) -> Unit
@@ -97,7 +93,7 @@ fun StorageDialog(
val dpSize = (characterSprite.value!!.width * 4 / density).dp val dpSize = (characterSprite.value!!.width * 4 / density).dp
Image( Image(
bitmap = imageBitmap, bitmap = imageBitmap,
contentDescription = stringResource(R.string.storage_character_image_description), contentDescription = "Character image",
filterQuality = FilterQuality.None, filterQuality = FilterQuality.None,
modifier = Modifier modifier = Modifier
.size(dpSize) .size(dpSize)
@@ -107,7 +103,7 @@ fun StorageDialog(
val nameDpSize = (characterName.value!!.width * 4 / density).dp val nameDpSize = (characterName.value!!.width * 4 / density).dp
Image( Image(
bitmap = nameImageBitmap, bitmap = nameImageBitmap,
contentDescription = stringResource(R.string.storage_character_image_description), contentDescription = "Character image",
filterQuality = FilterQuality.None, filterQuality = FilterQuality.None,
modifier = Modifier modifier = Modifier
.size(nameDpSize) .size(nameDpSize)
@@ -124,7 +120,7 @@ fun StorageDialog(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
) { ) {
Text(text = stringResource(R.string.storage_send_to_watch)) Text(text = "Send to watch")
} }
Spacer( Spacer(
modifier = Modifier modifier = Modifier
@@ -133,7 +129,7 @@ fun StorageDialog(
Button( Button(
onClick = onClickSetActive, onClick = onClickSetActive,
) { ) {
Text(text = stringResource(R.string.storage_set_active)) Text(text = "Set active")
} }
} }
Button( Button(
@@ -143,21 +139,14 @@ fun StorageDialog(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text(text = stringResource(R.string.storage_send_on_adventure)) Text(text = "Send on adventure")
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = onClickDelete
) {
Text(text = stringResource(R.string.storage_delete_character))
} }
Button( Button(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
onClick = onDismissRequest onClick = onDismissRequest
) { ) {
Text(text = stringResource(R.string.storage_close)) Text(text = "Close")
} }
} }
} }
@@ -11,15 +11,14 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -29,12 +28,12 @@ import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import androidx.compose.ui.res.stringResource import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable @Composable
@@ -43,23 +42,30 @@ fun StorageScreen(
storageScreenController: StorageScreenControllerImpl, storageScreenController: StorageScreenControllerImpl,
adventureScreenController: AdventureScreenControllerImpl adventureScreenController: AdventureScreenControllerImpl
) { ) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db) val storageRepository = StorageRepository(application.container.db)
val characterList by storageRepository.getAllCharacters().collectAsState(initial = emptyList()) val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) }
var selectedCharacter by remember { mutableStateOf<Long?>(null) } var selectedCharacter by remember { mutableStateOf<Long?>(null) }
LaunchedEffect(storageRepository, selectedCharacter) {
coroutineScope.launch {
val characterList = storageRepository.getAllCharacters()
monList.value = characterList
}
}
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = stringResource(R.string.storage_my_characters_title), text = "My characters",
onAdventureClick = { onAdventureClick = {
navController.navigate(NavigationItems.Adventure.route) navController.navigate(NavigationItems.Adventure.route)
} }
) )
} }
) { contentPadding -> ) { contentPadding ->
if (characterList.isEmpty()) { if (monList.value.isEmpty()) {
Column ( Column (
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
@@ -68,7 +74,7 @@ fun StorageScreen(
.fillMaxSize() .fillMaxSize()
) { ) {
Text( Text(
text = stringResource(R.string.storage_nothing_to_see_here), text = "Nothing to see here",
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier modifier = Modifier
) )
@@ -80,7 +86,7 @@ fun StorageScreen(
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical) .scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
items(characterList) { index -> items(monList.value) { index ->
CharacterEntry( CharacterEntry(
icon = BitmapData( icon = BitmapData(
bitmap = index.spriteIdle, bitmap = index.spriteIdle,
@@ -93,7 +99,7 @@ fun StorageScreen(
} else { } else {
Toast.makeText( Toast.makeText(
application, application,
application.getString(R.string.storage_in_adventure_toast), "This character is in an adventure",
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.navigate( navController.navigate(
@@ -101,16 +107,6 @@ fun StorageScreen(
) )
} }
}, },
cardColors = if (index.active) {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primary
)
} else {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
)
}
) )
} }
} }
@@ -142,16 +138,6 @@ fun StorageScreen(
timeInMinutes = time timeInMinutes = time
) )
selectedCharacter = null selectedCharacter = null
},
onClickDelete = {
storageScreenController
.deleteCharacter(
characterId = selectedCharacter!!,
onCompletion = {
AppLogger.d("StorageScreen", "Character deleted")
}
)
selectedCharacter = null
} }
) )
} }
@@ -2,5 +2,4 @@ package com.github.nacabaro.vbhelper.screens.storageScreen
interface StorageScreenController { interface StorageScreenController {
fun setActive(characterId: Long, onCompletion: () -> Unit) fun setActive(characterId: Long, onCompletion: () -> Unit)
fun deleteCharacter(characterId: Long, onCompletion: () -> Unit)
} }
@@ -28,21 +28,4 @@ class StorageScreenControllerImpl(
} }
} }
} }
override fun deleteCharacter(characterId: Long, onCompletion: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.userCharacterDao()
.deleteCharacterById(characterId)
componentActivity.runOnUiThread {
Toast.makeText(
componentActivity,
"Character deleted!",
Toast.LENGTH_SHORT
).show()
onCompletion()
}
}
}
} }
@@ -1,15 +0,0 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
class CardRepository (
private val db: AppDatabase
) {
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon> {
return db
.cardDao()
.getCardIconByCharaId(charaId = charaId)
}
}
@@ -1,28 +0,0 @@
package com.github.nacabaro.vbhelper.source
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class CurrencyRepository (
private val dataStore: DataStore<Preferences>
) {
private companion object {
val CURRENCY_VALUE = intPreferencesKey("currency_value")
}
val currencyValue: Flow<Int> = dataStore.data
.map { preferences ->
preferences[CURRENCY_VALUE] ?: 10000
}
suspend fun setCurrencyValue(newValue: Int) {
dataStore.edit { preferences ->
preferences[CURRENCY_VALUE] = newValue
}
}
}
@@ -3,24 +3,19 @@ package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.dtos.CardDtos import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
class DexRepository ( class DexRepository (
private val db: AppDatabase private val db: AppDatabase
) { ) {
fun getAllDims(): Flow<List<CardDtos.CardProgress>> { suspend fun getAllDims(): List<CardDtos.CardProgress> {
return db.dexDao().getCardsWithProgress() return db.dexDao().getCardsWithProgress()
} }
fun getCharactersByCardId(cardId: Long): Flow<List<CharacterDtos.CardCharaProgress>> { suspend fun getCharactersByCardId(cardId: Long): List<CharacterDtos.CardCharaProgress> {
return db.dexDao().getSingleCardProgress(cardId) return db.dexDao().getSingleCardProgress(cardId)
} }
fun getCharacterPossibleTransformations(characterId: Long): Flow<List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>> { suspend fun getCardPossibleTransformations(cardId: Long): List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained> {
return db.characterDao().getEvolutionRequirementsForCard(characterId) return db.characterDao().getEvolutionRequirementsForCard(cardId)
}
fun getCharacterPossibleFusions(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>> {
return db.cardFusionsDao().getFusionsForCharacter(characterId)
} }
} }
@@ -1,17 +1,17 @@
package com.github.nacabaro.vbhelper.source package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.items.Items
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
class ItemsRepository( class ItemsRepository(
private val db: AppDatabase private val db: AppDatabase
) { ) {
fun getAllItems(): Flow<List<ItemDtos.ItemsWithQuantities>> { suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities> {
return db.itemDao().getAllItems() return db.itemDao().getAllItems()
} }
fun getUserItems(): Flow<List<ItemDtos.ItemsWithQuantities>> { suspend fun getUserItems(): List<ItemDtos.ItemsWithQuantities> {
return db.itemDao().getAllUserItems() return db.itemDao().getAllUserItems()
} }
} }
@@ -1,13 +0,0 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.card.Card
import kotlinx.coroutines.flow.Flow
class ScanRepository(
val database: AppDatabase
) {
fun getCardDetails(characterId: Long): Flow<Card> {
return database.cardDao().getCardByCharacterId(characterId)
}
}
@@ -6,12 +6,11 @@ import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
class StorageRepository ( class StorageRepository (
private val db: AppDatabase private val db: AppDatabase
) { ) {
fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>> { suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites> {
return db.userCharacterDao().getAllCharacters() return db.userCharacterDao().getAllCharacters()
} }
@@ -19,19 +18,19 @@ class StorageRepository (
return db.userCharacterDao().getCharacterWithSprites(id) return db.userCharacterDao().getCharacterWithSprites(id)
} }
fun getCharacterBeData(id: Long): Flow<BECharacterData> { suspend fun getCharacterBeData(id: Long): BECharacterData {
return db.userCharacterDao().getBeData(id) return db.userCharacterDao().getBeData(id)
} }
fun getTransformationHistory(characterId: Long): Flow<List<CharacterDtos.TransformationHistory>> { suspend fun getTransformationHistory(characterId: Long): List<CharacterDtos.TransformationHistory>? {
return db.userCharacterDao().getTransformationHistory(characterId) return db.userCharacterDao().getTransformationHistory(characterId)
} }
fun getCharacterVbData(id: Long): Flow<VBCharacterData> { suspend fun getCharacterVbData(id: Long): VBCharacterData {
return db.userCharacterDao().getVbData(id) return db.userCharacterDao().getVbData(id)
} }
fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>> { suspend fun getSpecialMissions(id: Long): List<SpecialMissions> {
return db.userCharacterDao().getSpecialMissions(id) return db.userCharacterDao().getSpecialMissions(id)
} }
@@ -39,7 +38,7 @@ class StorageRepository (
return db.itemDao().getItem(id) return db.itemDao().getItem(id)
} }
fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?> { suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? {
return db.userCharacterDao().getActiveCharacter() return db.userCharacterDao().getActiveCharacter()
} }
@@ -47,7 +46,7 @@ class StorageRepository (
return db.userCharacterDao().deleteCharacterById(id) return db.userCharacterDao().deleteCharacterById(id)
} }
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>> { suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> {
return db.adventureDao().getAdventureCharacters() return db.adventureDao().getAdventureCharacters()
} }
@@ -1,9 +0,0 @@
<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,760q66,0 113,-47t47,-113v-160q0,-66 -47,-113t-113,-47q-66,0 -113,47t-47,113v160q0,66 47,113t113,47ZM400,640h160v-80L400,560v80ZM400,480h160v-80L400,400v80ZM480,520ZM480,840q-65,0 -120.5,-32T272,720L160,720v-80h84q-3,-20 -3.5,-40t-0.5,-40h-80v-80h80q0,-20 0.5,-40t3.5,-40h-84v-80h112q14,-23 31.5,-43t40.5,-35l-64,-66 56,-56 86,86q28,-9 57,-9t57,9l88,-86 56,56 -66,66q23,15 41.5,34.5T688,320h112v80h-84q3,20 3.5,40t0.5,40h80v80h-80q0,20 -0.5,40t-3.5,40h84v80L688,720q-32,56 -87.5,88T480,840Z"
android:fillColor="#000000"/>
</vector>
-220
View File
@@ -1,220 +0,0 @@
<resources>
<string name="app_name">VBHelper</string>
<string name="beta_warning_message_main">
Este aplicativo ainda está em versão alfa e não está completo. Não o use para armazenar personagens importantes para você, pois futuras atualizações podem apagar todos os seus personagens. Desculpe pelo transtorno!
</string>
<string name="beta_warning_message_compatibility">
O aplicativo agora deve funcionar com o VB original e com o VH.
</string>
<string name="beta_warning_message_thanks">
Obrigado pela compreensão e pela paciência. Atenciosamente, a equipe de desenvolvimento.
</string>
<string name="beta_warning_button_dismiss">Fechar</string>
<string name="nav_scan">Escanear</string>
<string name="nav_battle">Batalhas</string>
<string name="nav_home">Início</string>
<string name="nav_dex">Dex</string>
<string name="nav_card_adventure">Aventura do card</string>
<string name="nav_storage">Armazenamento</string>
<string name="nav_settings">Configurações</string>
<string name="nav_viewer">Visualizador</string>
<string name="nav_card">Card</string>
<string name="nav_items">Itens</string>
<string name="nav_my_items">Meus itens</string>
<string name="nav_items_store">Loja de itens</string>
<string name="nav_apply_item">Aplicar item</string>
<string name="nav_adventure">Aventura</string>
<string name="nav_credits">Créditos</string>
<string name="adventure_title">Aventura</string>
<string name="adventure_empty_state">Nada para ver aqui</string>
<string name="home_title">VB Helper</string>
<string name="home_adventure_mission_finished">
Um dos seus personagens terminou a missão de aventura!
</string>
<string name="scan_secrets_not_initialized">
Os segredos ainda não foram inicializados. Tente novamente.
</string>
<string name="scan_secrets_not_imported">
Os segredos ainda não foram importados. Vá em Configurações e importe o APK.
</string>
<string name="scan_title">Escanear um Vital Bracelet</string>
<string name="scan_vb_to_app">Vital Bracelet para o app</string>
<string name="scan_app_to_vb">App para o Vital Bracelet</string>
<string name="scan_no_nfc_on_device">O dispositivo não possui NFC!</string>
<string name="scan_tag_not_vb">A tag detectada não é do Vital Bracelet.</string>
<string name="scan_missing_secrets">
Segredos ausentes. Vá em Configurações e importe o APK do Vital Arena.
</string>
<string name="scan_sent_character_success">Personagem enviado com sucesso!</string>
<string name="scan_error_generic">Ops!</string>
<string name="scan_sent_dim_success">DIM enviado com sucesso!</string>
<string name="scan_nfc_must_be_enabled">O NFC precisa estar ativado.</string>
<string name="settings_title">Configurações</string>
<string name="settings_section_nfc">Comunicação NFC</string>
<string name="settings_section_dim_bem">Gerenciamento de DiM/BEm</string>
<string name="settings_section_about">Sobre e créditos</string>
<string name="settings_section_data">Gerenciamento de dados</string>
<string name="settings_import_apk_title">Importar APK</string>
<string name="settings_import_apk_desc">
Importar segredos do APK do Vital Arena 2.1.0
</string>
<string name="settings_import_card_title">Importar card</string>
<string name="settings_import_card_desc">
Importar arquivo de card DiM/BEm
</string>
<string name="settings_credits_title">Créditos</string>
<string name="settings_credits_desc">Créditos</string>
<string name="settings_about_title">Sobre</string>
<string name="settings_about_desc">Sobre</string>
<string name="settings_export_data_title">Exportar dados</string>
<string name="settings_export_data_desc">
Exportar banco de dados do aplicativo
</string>
<string name="settings_import_data_title">Importar dados</string>
<string name="settings_import_data_desc">
Importar banco de dados do aplicativo
</string>
<string name="credits_title">Créditos</string>
<string name="credits_section_reverse_engineering">Engenharia reversa</string>
<string name="credits_section_app_development">Desenvolvimento do aplicativo</string>
<string name="credits_cyanic_description">
Reverteu o firmware e nos ajudou durante o desenvolvimento.
</string>
<string name="credits_cfogrady_description">
Desenvolveu vb-lib-nfc e parte deste aplicativo.
</string>
<string name="credits_nacabaro_description">
Desenvolveu este aplicativo.
</string>
<string name="credits_lightheel_description">
Está desenvolvendo a parte de batalhas deste aplicativo, incluindo o servidor. Ainda em desenvolvimento.
</string>
<string name="credits_shvstrz_description">
Responsável pelo design do ícone do aplicativo em SVG.
</string>
<string name="action_place_near_reader">Aproxime o seu Vital Bracelet do leitor...</string>
<string name="action_cancel">Cancelar</string>
<string name="read_character_title">Ler personagem</string>
<string name="read_character_prepare_device">Prepare seu dispositivo!</string>
<string name="read_character_go_to_connect">Vá em conectar e, quando estiver pronto, pressione confirmar!</string>
<string name="read_character_confirm">Confirmar</string>
<string name="reading_character_title">Lendo personagem</string>
<string name="write_card_title">Escrevendo detalhes do cartão</string>
<string name="write_card_icon_description">Ícone do cartão</string>
<string name="write_card_device_ready">Prepare o seu dispositivo!</string>
<string name="write_card_required_card">Você vai precisar do cartão %1$s!</string>
<string name="write_card_confirm">Confirmar</string>
<string name="write_character_title">Escrevendo personagem</string>
<string name="write_character_icon_description">Ícone do personagem</string>
<string name="write_character_success">
Cartão instalado com sucesso!!
</string>
<string name="write_character_wait_ready">
Aguarde até que seu dispositivo esteja pronto e então toque em Confirmar
</string>
<string name="write_character_confirm">Confirmar</string>
<string name="sending_card_title">Enviando cartão</string>
<string name="writing_character_action_title">Escrevendo personagem</string>
<string name="items_title">Itens</string>
<string name="items_no_items">Nenhum item</string>
<string name="items_store_credits">%1$d créditos</string>
<string name="items_not_enough_credits">Créditos insuficientes</string>
<string name="items_purchase_success">Compra realizada com sucesso!</string>
<string name="obtained_item_you_have">Você obteve %1$d deste item</string>
<string name="obtained_item_you_also_got_credits">Você também recebeu %1$d créditos</string>
<string name="obtained_item_dismiss">Fechar</string>
<string name="choose_character_title">Escolher personagem</string>
<string name="choose_character_item_applied">Item aplicado!</string>
<string name="item_dialog_costs_credits">Custa %1$d créditos</string>
<string name="item_dialog_you_have_quantity">Você tem %1$d deste item</string>
<string name="item_dialog_use">Usar item</string>
<string name="item_dialog_purchase">Comprar</string>
<string name="item_dialog_cancel">Cancelar</string>
<string name="battles_online_title">Batalhas online</string>
<string name="battles_coming_soon">Em breve</string>
<string name="cards_my_cards_title">Meus cards</string>
<string name="card_view_discovered_characters">Personagens descobertos</string>
<string name="dex_chara_icon_description">Ícone do personagem</string>
<string name="dex_chara_name_icon_description">Nome do personagem</string>
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute">Stg: %1$s, Atr: %2$s</string>
<string name="dex_chara_unknown_name">\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</string>
<string name="dex_chara_stats_unknown">HP: -, BP: -, AP: -</string>
<string name="dex_chara_requirements">
Tr: %1$d; Bt: %2$d; Vr: %3$d; Wr: %4$d%%; Ct: %5$dh
</string>
<string name="dex_chara_adventure_level">AdvLvl %1$d</string>
<string name="dex_chara_fusions_button">Fusões</string>
<string name="dex_chara_close_button">Fechar</string>
<string name="card_adventure_missions_title">Missões de aventura</string>
<string name="card_entry_characters_obtained">%1$d de %2$d personagens obtidos</string>
<string name="card_entry_edit">Editar card</string>
<string name="card_entry_delete">Excluir card</string>
<string name="storage_my_characters_title">Meus personagens</string>
<string name="storage_nothing_to_see_here">Nada para ver aqui</string>
<string name="storage_in_adventure_toast">Este personagem está em uma aventura</string>
<string name="storage_character_image_description">Imagem do personagem</string>
<string name="storage_send_to_watch">Enviar para o relógio</string>
<string name="storage_set_active">Definir como ativo</string>
<string name="storage_send_on_adventure">Enviar para aventura</string>
<string name="storage_delete_character">Excluir personagem</string>
<string name="storage_close">Fechar</string>
<string name="home_vbdim_vitals">Vitais</string>
<string name="home_vbdim_trophies">Troféus</string>
<string name="home_vbdim_mood">Humor</string>
<string name="home_vbdim_next_timer">Próximo timer</string>
<string name="home_vbdim_total_battle_win">% vitórias totais</string>
<string name="home_vbdim_current_phase_win">% vitórias fase atual</string>
<string name="home_vbdim_special_missions">Missões especiais</string>
<string name="special_mission_none">Nenhuma missão selecionada</string>
<string name="special_mission_steps">Ande %1$d passos</string>
<string name="special_mission_battles">Lute %1$d vezes</string>
<string name="special_mission_wins">Vença %1$d batalhas</string>
<string name="special_mission_vitals">Ganhe %1$d vitals</string>
<string name="special_mission_steps_progress">Andou %1$d passos</string>
<string name="special_mission_battles_progress">Lutou %1$d vezes</string>
<string name="special_mission_wins_progress">Venceu %1$d batalhas</string>
<string name="special_mission_vitals_progress">Ganhou %1$d vitals</string>
<string name="special_mission_icon_content_description">Ícone de missão especial</string>
</resources>
-230
View File
@@ -1,233 +1,3 @@
<resources> <resources>
<string name="app_name">VBHelper</string> <string name="app_name">VBHelper</string>
<string name="beta_warning_message_main">
This application is currently in alpha and it is not complete. Do not use it to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!
</string>
<string name="beta_warning_message_compatibility">
The application should now work with the original VB and the VH.
</string>
<string name="beta_warning_message_thanks">
Thank you for your understanding and patience. Sincerely, the dev team.
</string>
<string name="beta_warning_button_dismiss">
Dismiss
</string>
<string name="nav_scan">Scan</string>
<string name="nav_battle">Battle</string>
<string name="nav_home">Home</string>
<string name="nav_dex">Dex</string>
<string name="nav_card_adventure">Card adventure</string>
<string name="nav_storage">Storage</string>
<string name="nav_settings">Settings</string>
<string name="nav_viewer">Viewer</string>
<string name="nav_card">Card</string>
<string name="nav_items">Items</string>
<string name="nav_my_items">My items</string>
<string name="nav_items_store">Items store</string>
<string name="nav_apply_item">Apply item</string>
<string name="nav_adventure">Adventure</string>
<string name="nav_credits">Credits</string>
<string name="adventure_title">Adventure</string>
<string name="adventure_empty_state">Nothing to see here</string>
<string name="home_title">VB Helper</string>
<string name="home_adventure_mission_finished">
One of your characters has finished their adventure mission!
</string>
<string name="scan_secrets_not_initialized">
Secrets is not yet initialized. Try again.
</string>
<string name="scan_secrets_not_imported">
Secrets not yet imported. Go to Settings and Import APK.
</string>
<string name="scan_title">Scan a Vital Bracelet</string>
<string name="scan_vb_to_app">Vital Bracelet to App</string>
<string name="scan_app_to_vb">App to Vital Bracelet</string>
<string name="scan_no_nfc_on_device">No NFC on device!</string>
<string name="scan_tag_not_vb">Tag detected is not VB</string>
<string name="scan_missing_secrets">
Missing Secrets. Go to settings and import Vital Arena APK.
</string>
<string name="scan_sent_character_success">Sent character successfully!</string>
<string name="scan_error_generic">Whoops</string>
<string name="scan_sent_dim_success">Sent DIM successfully!</string>
<string name="scan_nfc_must_be_enabled">NFC must be enabled</string>
<string name="settings_title">Settings</string>
<string name="settings_section_nfc">NFC Communication</string>
<string name="settings_section_dim_bem">DiM/BEm management</string>
<string name="settings_section_about">About and credits</string>
<string name="settings_section_data">Data management</string>
<string name="settings_import_apk_title">Import APK</string>
<string name="settings_import_apk_desc">
Import Secrets From Vital Arena 2.1.0 APK
</string>
<string name="settings_import_card_title">Import card</string>
<string name="settings_import_card_desc">
Import DiM/BEm card file
</string>
<string name="settings_credits_title">Credits</string>
<string name="settings_credits_desc">Credits</string>
<string name="settings_about_title">About</string>
<string name="settings_about_desc">About</string>
<string name="settings_export_data_title">Export data</string>
<string name="settings_export_data_desc">
Export application database
</string>
<string name="settings_import_data_title">Import data</string>
<string name="settings_import_data_desc">
Import application database
</string>
<string name="credits_title">Credits</string>
<string name="credits_section_reverse_engineering">Reverse engineering</string>
<string name="credits_section_app_development">Application development</string>
<string name="credits_cyanic_description">
Reversed the firmware and helped us during development.
</string>
<string name="credits_cfogrady_description">
Developed vb-lib-nfc and part of this application.
</string>
<string name="credits_nacabaro_description">
Developed this application.
</string>
<string name="credits_lightheel_description">
Developing the battling part for this application, including server. Still in the works.
</string>
<string name="credits_shvstrz_description">
Designing the app icon in SVG.
</string>
<string name="action_place_near_reader">Place your Vital Bracelet near the reader...</string>
<string name="action_cancel">Cancel</string>
<string name="read_character_title">Read character</string>
<string name="read_character_prepare_device">Prepare your device!</string>
<string name="read_character_go_to_connect">Go to connect and when ready press confirm!</string>
<string name="read_character_confirm">Confirm</string>
<string name="reading_character_title">Reading character</string>
<string name="write_card_title">Writing card details</string>
<string name="write_card_icon_description">Card icon</string>
<string name="write_card_device_ready">Get your device ready!</string>
<string name="write_card_required_card">You will need %1$s card!</string>
<string name="write_card_confirm">Confirm</string>
<string name="write_character_title">Writing character</string>
<string name="write_character_icon_description">Character icon</string>
<string name="write_character_success">
Card installed successfully!!
</string>
<string name="write_character_wait_ready">
Wait until your device is ready, then tap Confirm
</string>
<string name="write_character_confirm">Confirm</string>
<string name="sending_card_title">Sending card</string>
<string name="writing_character_action_title">Writing character</string>
<string name="items_title">Items</string>
<string name="items_no_items">No items</string>
<string name="items_store_credits">%1$d credits</string>
<string name="items_not_enough_credits">Not enough credits</string>
<string name="items_purchase_success">Purchase successful!</string>
<string name="obtained_item_you_have">You have obtained %1$d of this item</string>
<string name="obtained_item_you_also_got_credits">You also got %1$d credits</string>
<string name="obtained_item_dismiss">Dismiss</string>
<string name="choose_character_title">Choose character</string>
<string name="choose_character_item_applied">Item applied!</string>
<string name="item_dialog_costs_credits">Costs %1$d credits</string>
<string name="item_dialog_you_have_quantity">You have %1$d of this item</string>
<string name="item_dialog_use">Use item</string>
<string name="item_dialog_purchase">Purchase</string>
<string name="item_dialog_cancel">Cancel</string>
<string name="battles_online_title">Online battles</string>
<string name="battles_coming_soon">Coming soon</string>
<string name="cards_my_cards_title">My cards</string>
<string name="card_view_discovered_characters">Discovered characters</string>
<string name="dex_chara_icon_description">Character icon</string>
<string name="dex_chara_name_icon_description">Character name</string>
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute">Stg: %1$s, Atr: %2$s</string>
<string name="dex_chara_unknown_name">\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</string>
<string name="dex_chara_stats_unknown">HP: -, BP: -, AP: -</string>
<string name="dex_chara_requirements">
Tr: %1$d; Bt: %2$d; Vr: %3$d; Wr: %4$d%%; Ct: %5$dh
</string>
<string name="dex_chara_adventure_level">AdvLvl %1$d</string>
<string name="dex_chara_fusions_button">Fusions</string>
<string name="dex_chara_close_button">Close</string>
<string name="card_adventure_missions_title">Adventure missions</string>
<string name="card_entry_characters_obtained">%1$d of %2$d characters obtained</string>
<string name="card_entry_edit">Edit card</string>
<string name="card_entry_delete">Delete card</string>
<string name="storage_my_characters_title">My characters</string>
<string name="storage_nothing_to_see_here">Nothing to see here</string>
<string name="storage_in_adventure_toast">This character is in an adventure</string>
<string name="storage_character_image_description">Character image</string>
<string name="storage_send_to_watch">Send to watch</string>
<string name="storage_set_active">Set active</string>
<string name="storage_send_on_adventure">Send on adventure</string>
<string name="storage_delete_character">Delete character</string>
<string name="storage_close">Close</string>
<string name="home_vbdim_vitals">Vitals</string>
<string name="home_vbdim_trophies">Trophies</string>
<string name="home_vbdim_mood">Mood</string>
<string name="home_vbdim_next_timer">Next timer</string>
<string name="home_vbdim_total_battle_win">Total battle win %</string>
<string name="home_vbdim_current_phase_win">Current phase win %</string>
<string name="home_vbdim_special_missions">Special missions</string>
<string name="special_mission_none">No mission selected</string>
<string name="special_mission_steps">Walk %1$d steps</string>
<string name="special_mission_battles">Battle %1$d times</string>
<string name="special_mission_wins">Win %1$d battles</string>
<string name="special_mission_vitals">Earn %1$d vitals</string>
<string name="special_mission_steps_progress">Walked %1$d steps</string>
<string name="special_mission_battles_progress">Battled %1$d times</string>
<string name="special_mission_wins_progress">Won %1$d battles</string>
<string name="special_mission_vitals_progress">Earned %1$d vitals</string>
<string name="special_mission_icon_content_description">Special mission icon</string>
<string name="home_special_mission_delete_main">Are you sure you want to delete this special mission with this progress?</string>
<string name="home_special_mission_delete_dismiss">Dismiss</string>
<string name="home_special_mission_delete_remove">Remove</string>
<string name="nav_logs">Logs</string>
<string name="settings_logs_title">Log viewer</string>
<string name="settings_logs_desc">Check logs generated within the app</string>
<string name="logs_title">Application logs</string>
</resources> </resources>
+12 -21
View File
@@ -1,32 +1,24 @@
[versions] [versions]
agp = "8.13.2" agp = "8.12.3"
datastore = "1.2.0" datastore = "1.1.2"
datastorePreferences = "1.2.0" kotlin = "2.0.0"
kotlin = "2.3.0" coreKtx = "1.15.0"
coreKtx = "1.17.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.3.0" junitVersion = "1.2.1"
espressoCore = "3.7.0" espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.10.0" lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.12.2" activityCompose = "1.10.0"
composeBom = "2026.01.00" composeBom = "2025.01.00"
material = "1.13.0" protobufGradlePlugin = "0.9.4"
navigationCompose = "2.9.6" protobufJavalite = "4.27.0"
protobufGradlePlugin = "0.9.6" roomRuntime = "2.6.1"
protobufJavalite = "4.33.4"
roomRuntime = "2.8.4"
vbNfcReader = "0.2.0-SNAPSHOT" vbNfcReader = "0.2.0-SNAPSHOT"
dimReader = "2.1.0" dimReader = "2.1.0"
[libraries] [libraries]
androidx-compose-material = { module = "androidx.compose.material:material" }
androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
@@ -41,7 +33,6 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
material = { module = "com.google.android.material:material", version.ref = "material" }
protobuf-gradle-plugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version.ref = "protobufGradlePlugin" } protobuf-gradle-plugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version.ref = "protobufGradlePlugin" }
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobufJavalite" } protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobufJavalite" }
vb-nfc-reader = { module = "com.github.cfogrady:vb-nfc-reader", version.ref = "vbNfcReader" } vb-nfc-reader = { module = "com.github.cfogrady:vb-nfc-reader", version.ref = "vbNfcReader" }