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
70 changed files with 1177 additions and 2180 deletions
+35 -66
View File
@@ -1,69 +1,38 @@
# VBHelper
Application to interact with the Vital series, VB, VH, VBC and VBBE.
## Developer Setup
## Current state of the project
Right now the project is still under development, and until further notice, any database updates will result in having to erase application data.
This document will be updated once the application does not need any more database resets.
## Features
As of now, the project allows you to read characters, view characters stats, and send them back to your watch.
You can also apply items to the characters read, such as special missions, or change timers, and store characters in the storage section.
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.
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.
## How to set up
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:
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.
2. Once the files are extracted, look for an APK called `com.bandai.vitalbraceletarena.apk`. Copy it somewhere else, you will need it.
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.
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.
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`.
You will be prompted to choose a file. Choose the APK file that was previously obtained.
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.
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.
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.
3. Clone vb-dim-reader (https://github.com/cfogrady/vb-dim-reader)
4. Run publishToMavenLocal gradle task in the vb-dim-reader project.
5. Create res/values/keys.xml within the app module.
6. Populate with:
```
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="password1">beHmacKey1</string>
<string name="password2">beHmacKey2</string>
<string name="decryptionKey">aesKey</string>
<integer-array name="substitutionArray">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
<item>14</item>
<item>15</item>
</integer-array>
</resources>
```
7. Replace the values in the keys.xml file with those extracted from the original app.
8. Run
+1 -2
View File
@@ -15,7 +15,7 @@ android {
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "Alpha 0.6.2"
versionName = "Alpha 0.6.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -91,5 +91,4 @@ dependencies {
implementation("com.google.android.material:material:1.2.0")
implementation(libs.protobuf.javalite)
implementation("androidx.compose.material:material")
implementation("androidx.datastore:datastore-preferences:1.1.7")
}
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -45,9 +44,6 @@ fun CharacterEntry(
disabled: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 4,
cardColors: CardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
),
onClick: () -> Unit = { }
) {
val bitmap = remember (icon.bitmap) {
@@ -65,8 +61,7 @@ fun CharacterEntry(
},
modifier = modifier
.aspectRatio(1f)
.padding(8.dp),
colors = cardColors
.padding(8.dp)
) {
Box(
contentAlignment = Alignment.BottomCenter,
@@ -3,7 +3,6 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
@@ -38,7 +37,7 @@ interface AdventureDao {
JOIN Adventure a ON uc.id = a.characterId
"""
)
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>>
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites>
@Query("""
DELETE FROM Adventure
@@ -3,7 +3,6 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CardAdventureDao {
@@ -53,7 +52,7 @@ interface CardAdventureDao {
WHERE
cc.cardId = :cardId
""")
fun getAdventureForCard(
suspend fun getAdventureForCard(
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,7 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.Card
import kotlinx.coroutines.flow.Flow
import com.github.nacabaro.vbhelper.domain.card.CardSprites
@Dao
interface CardDao {
@@ -27,11 +27,18 @@ interface CardDao {
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")
suspend fun renameCard(id: Int, newName: String)
@Query("DELETE FROM Card WHERE id = :id")
suspend fun deleteCard(id: Long)
@Insert
suspend fun insertSprites(vararg sprites: CardSprites)
@Query("SELECT * FROM CardSprites")
suspend fun getSprites(): List<CardSprites>
}
@@ -2,47 +2,33 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CardFusionsDao {
@Query("""
@Query(
"""
INSERT INTO
CardFusions (
fromCharaId,
attribute,
toCharaId
toVirusFusion,
toDataFusion,
toVaccineFusion,
toFreeFusion
)
SELECT
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :fromCharaId),
:attribute,
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaId)
""")
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaIdVirus),
(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(
cardId: Long,
fromCharaId: Int,
attribute: NfcCharacter.Attribute,
toCharaId: Int
toCharaIdVirus: 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
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CardProgressDao {
@Query("""
UPDATE CardProgress
SET
currentStage = :currentStage,
unlocked = :unlocked
WHERE cardId = :cardId AND
currentStage < :currentStage
""")
fun updateCardProgress(currentStage: Int, cardId: Long, unlocked: Boolean)
@Upsert
fun updateDimProgress(vararg cardProgresses: CardProgress)
@Query(
"SELECT currentStage FROM CardProgress WHERE cardId = :cardId"
)
fun getCardProgress(cardId: Long): Flow<Int>
@Insert
fun insertCardProgress(cardProgress: CardProgress)
fun getCardProgress(cardId: Long): Int
}
@@ -6,7 +6,6 @@ import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CharacterDao {
@@ -25,12 +24,10 @@ interface CharacterDao {
d.cardId as cardId,
c.charaIndex as charId,
c.stage as stage,
c.attribute as attribute,
cp.currentStage as currentStage
c.attribute as attribute
FROM CardCharacter c
JOIN UserCharacter uc ON c.id = uc.charId
JOIN Card d ON c.cardId = d.id
JOIN CardProgress cp ON d.id = cp.cardId
WHERE c.id = :charId
"""
)
@@ -38,7 +35,7 @@ interface CharacterDao {
@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 id FROM CardCharacter WHERE charaIndex = :fromChraraIndex AND cardId = :cardId),
:requiredVitals,
@@ -78,13 +75,13 @@ interface CharacterDao {
pt.changeTimerHours as changeTimerHours,
pt.requiredAdventureLevelCompleted as requiredAdventureLevelCompleted
FROM
PossibleTransformations pt
CardTransformations pt
JOIN CardCharacter c on pt.toCharaId = c.id
JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Dex d ON d.id = pt.toCharaId
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 com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface DexDao {
@@ -41,7 +40,7 @@ interface DexDao {
WHERE c.cardId = :cardId
"""
)
fun getSingleCardProgress(cardId: Long): Flow<List<CharacterDtos.CardCharaProgress>>
suspend fun getSingleCardProgress(cardId: Long): List<CharacterDtos.CardCharaProgress>
@Query(
"""
@@ -56,5 +55,5 @@ interface DexDao {
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.Query
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface ItemDao {
@@ -14,7 +13,7 @@ interface ItemDao {
ORDER BY Items.itemIcon ASC
"""
)
fun getAllItems(): Flow<List<ItemDtos.ItemsWithQuantities>>
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities>
@Query(
"""
@@ -23,7 +22,7 @@ interface ItemDao {
WHERE quantity > 0
"""
)
fun getAllUserItems(): Flow<List<ItemDtos.ItemsWithQuantities>>
suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>
@Query(
"""
@@ -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.VitalsHistory
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface UserCharacterDao {
@@ -69,8 +68,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure,
uc.isActive as active
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId
@@ -78,7 +76,7 @@ interface UserCharacterDao {
LEFT JOIN Adventure a ON a.characterId = uc.id
"""
)
fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>>
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
@Query(
"""
@@ -94,8 +92,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure,
uc.isActive as active
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = d.id
@@ -132,8 +129,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId as isInAdventure,
uc.isActive as active
a.characterId as isInAdventure
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = d.id
@@ -197,8 +193,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure,
uc.isActive as active
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId
@@ -223,8 +218,7 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure,
uc.isActive as active
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId
@@ -4,6 +4,7 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.daos.AdventureDao
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.DexDao
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.SpriteDao
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.Card
import com.github.nacabaro.vbhelper.domain.card.CardAdventure
import com.github.nacabaro.vbhelper.domain.card.CardFusions
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.Adventure
import com.github.nacabaro.vbhelper.domain.characters.Dex
@@ -39,6 +41,7 @@ import com.github.nacabaro.vbhelper.domain.items.Items
CardCharacter::class,
CardAdventure::class,
CardFusions::class,
CardSprites::class,
Sprite::class,
UserCharacter::class,
BECharacterData::class,
@@ -49,8 +52,8 @@ import com.github.nacabaro.vbhelper.domain.items.Items
Dex::class,
Items::class,
Adventure::class,
Background::class,
PossibleTransformations::class
CardBackground::class,
CardTransformations::class
]
)
abstract class AppDatabase : RoomDatabase() {
@@ -65,4 +68,5 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun specialMissionDao(): SpecialMissionDao
abstract fun cardAdventureDao(): CardAdventureDao
abstract fun cardFusionsDao(): CardFusionsDao
abstract fun cardBackgroundDao(): CardBackgroundDao
}
@@ -1,11 +1,9 @@
package com.github.nacabaro.vbhelper.di
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
interface AppContainer {
val db: AppDatabase
val dataStoreSecretsRepository: DataStoreSecretsRepository
val currencyRepository: CurrencyRepository
}
@@ -1,27 +1,20 @@
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.dataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import androidx.room.Room
import com.github.nacabaro.vbhelper.database.AppDatabase
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.SecretsSerializer
import com.github.nacabaro.vbhelper.source.proto.Secrets
private const val SECRETS_DATA_STORE_NAME = "secrets.pb"
private const val USER_PREFERENCES_NAME = "user_preferences"
val Context.secretsStore: DataStore<Secrets> by dataStore(
fileName = SECRETS_DATA_STORE_NAME,
serializer = SecretsSerializer
)
val Context.currencyStore: DataStore<Preferences> by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
class DefaultAppContainer(private val context: Context) : AppContainer {
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 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 (
@PrimaryKey(autoGenerate = true) val id: Long,
data class CardBackground (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val cardId: Long,
val background: ByteArray,
val backgroundWidth: Int,
@@ -3,7 +3,6 @@ package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter
@Entity(
foreignKeys = [
@@ -16,7 +15,25 @@ import com.github.cfogrady.vbnfc.data.NfcCharacter
ForeignKey(
entity = CardCharacter::class,
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
)
]
@@ -24,6 +41,8 @@ import com.github.cfogrady.vbnfc.data.NfcCharacter
data class CardFusions(
@PrimaryKey(autoGenerate = true) val id: Long,
val fromCharaId: Long,
val attribute: NfcCharacter.Attribute,
val toCharaId: Long
val toVirusFusion: 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,
var charaId: Long,
val requiredVitals: Int,
@@ -31,16 +31,14 @@ object CharacterDtos {
val nameSpriteWidth: Int,
val nameSpriteHeight: Int,
val isBemCard: Boolean,
val isInAdventure: Boolean,
val active: Boolean
val isInAdventure: Boolean
)
data class CardCharacterInfo(
val cardId: Long,
val charId: Int,
val stage: Int,
val attribute: NfcCharacter.Attribute,
val currentStage: Int
val attribute: NfcCharacter.Attribute
)
data class TransformationHistory(
@@ -115,14 +113,4 @@ object CharacterDtos {
val changeTimerHours: 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
)
}
@@ -9,11 +9,12 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
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.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -28,6 +29,7 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.Instant
@Composable
@@ -35,17 +37,16 @@ fun AdventureScreen(
navController: NavController,
storageScreenController: AdventureScreenControllerImpl
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val storageRepository = StorageRepository(database)
val characterList by storageRepository.getAdventureCharacters().collectAsState(emptyList())
val characterList = remember {
mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
}
var obtainedItem by remember {
mutableStateOf<ItemDtos.PurchasedItem?>(null)
}
var obtainedCurrency by remember {
mutableStateOf(0)
}
val currentTime by produceState(initialValue = Instant.now().epochSecond) {
while (true) {
@@ -58,6 +59,13 @@ fun AdventureScreen(
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
}
LaunchedEffect(storageRepository) {
coroutineScope.launch {
characterList.value = storageRepository
.getAdventureCharacters()
}
}
Scaffold(
topBar = {
TopBanner(
@@ -68,7 +76,7 @@ fun AdventureScreen(
)
}
) { contentPadding ->
if (characterList.isEmpty()) {
if (characterList.value.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -83,7 +91,7 @@ fun AdventureScreen(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(characterList) {
items(characterList.value) {
AdventureEntry(
icon = BitmapData(
bitmap = it.spriteIdle,
@@ -94,9 +102,8 @@ fun AdventureScreen(
onClick = {
if (it.finishesAdventure < currentTime) {
storageScreenController
.getItemFromAdventure(it.id) { adventureResult, generatedCurrency ->
.getItemFromAdventure(it.id) { adventureResult ->
obtainedItem = adventureResult
obtainedCurrency = generatedCurrency
}
} else {
cancelAdventureDialog = it
@@ -111,7 +118,6 @@ fun AdventureScreen(
if (obtainedItem != null) {
ObtainedItemDialog(
obtainedItem = obtainedItem!!,
obtainedCurrency = obtainedCurrency,
onClickDismiss = {
obtainedItem = null
}
@@ -4,6 +4,6 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface AdventureScreenController {
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)
}
@@ -6,7 +6,6 @@ import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
import kotlin.random.Random
@@ -38,29 +37,19 @@ class AdventureScreenControllerImpl(
override fun getItemFromAdventure(
characterId: Long,
onResult: (ItemDtos.PurchasedItem, Int) -> Unit
onResult: (ItemDtos.PurchasedItem) -> Unit
) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.adventureDao()
.deleteAdventure(characterId)
val generatedCurrency = generateRandomCurrency()
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) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
@@ -88,7 +77,6 @@ class AdventureScreenControllerImpl(
val randomItem = database
.itemDao()
.getAllItems()
.first()
.random()
val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()
@@ -6,11 +6,15 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun CardAdventureScreen(
@@ -18,12 +22,20 @@ fun CardAdventureScreen(
cardScreenController: CardScreenControllerImpl,
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)
.collectAsState(emptyList())
val currentCardAdventure by cardScreenController
currentCardAdventure =
cardScreenController
.getCardProgress(cardId)
.collectAsState(0)
}
}
Scaffold (
topBar = {
@@ -40,10 +52,10 @@ fun CardAdventureScreen(
.padding(top = contentPadding.calculateTopPadding())
.verticalScroll(state = rememberScrollState())
) {
cardAdventureMissions.mapIndexed { index, it ->
cardAdventureMissions.value.mapIndexed { index, it ->
CardAdventureEntry(
cardAdventureEntry = it,
obscure = index > currentCardAdventure - 1
obscure = index > currentCardAdventure
)
}
}
@@ -1,13 +1,10 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
interface CardScreenController {
fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit)
fun deleteCard(cardId: Long, onDeleted: () -> Unit)
fun getCardAdventureMissions(cardId: Long): Flow<List<CardDtos.CardAdventureWithSprites>>
fun getCardProgress(cardId: Long): Flow<Int>
fun getFusionsForCharacters(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>>
suspend fun getCardAdventureMissions(cardId: Long): List<CardDtos.CardAdventureWithSprites>
suspend fun getCardProgress(cardId: Long): Int
}
@@ -4,8 +4,6 @@ import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
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
.cardAdventureDao()
.getAdventureForCard(cardId)
}
override fun getCardProgress(cardId: Long): Flow<Int> {
override suspend fun getCardProgress(cardId: Long): Int {
return database
.cardProgressDao()
.getCardProgress(cardId)
}
override fun getFusionsForCharacters(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>> {
return database
.cardFusionsDao()
.getFusionsForCharacter(characterId)
}
}
@@ -5,10 +5,10 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData
@@ -19,19 +19,32 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.DexCharaDetailsDialog
import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch
@Composable
fun CardViewScreen(
navController: NavController,
cardId: Long
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
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) }
LaunchedEffect(dexRepository) {
coroutineScope.launch {
val newCharacterList = dexRepository.getCharactersByCardId(cardId)
characterList.value = newCharacterList
val newCardPossibleTransformations = dexRepository.getCardPossibleTransformations(cardId)
cardPossibleTransformations.value = newCardPossibleTransformations
}
}
Scaffold (
topBar = {
TopBanner(
@@ -57,7 +70,7 @@ fun CardViewScreen(
columns = GridCells.Fixed(3),
contentPadding = contentPadding
) {
items(characterList) { character ->
items(characterList.value) { character ->
CharacterEntry(
onClick = {
selectedCharacter.value = character
@@ -75,6 +88,7 @@ fun CardViewScreen(
if (selectedCharacter.value != null) {
DexCharaDetailsDialog(
currentChara = selectedCharacter.value!!,
possibleTransformations = cardPossibleTransformations.value,
obscure = selectedCharacter.value!!.discoveredOn == null,
onClickClose = {
selectedCharacter.value = null
@@ -7,10 +7,11 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -24,15 +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.CardRenameDialog
import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch
@Composable
fun CardsScreen(
navController: NavController,
cardScreenController: CardScreenControllerImpl
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
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) }
var clickedDelete by remember { mutableStateOf(false) }
@@ -40,6 +43,13 @@ fun CardsScreen(
var modifyCards by remember { mutableStateOf(false) }
LaunchedEffect(dexRepository) {
coroutineScope.launch {
val newDimList = dexRepository.getAllDims()
cardList.value = newDimList
}
}
Scaffold (
topBar = {
TopBanner(
@@ -54,7 +64,7 @@ fun CardsScreen(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(cardList) {
items(cardList.value) {
CardEntry(
name = it.cardName,
logo = BitmapData(
@@ -16,11 +16,6 @@ import androidx.compose.material3.MaterialTheme
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.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
@@ -28,9 +23,7 @@ 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.di.VBHelper
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.getImageBitmap
@@ -38,25 +31,14 @@ import com.github.nacabaro.vbhelper.utils.getImageBitmap
@Composable
fun DexCharaDetailsDialog(
currentChara: CharacterDtos.CardCharaProgress,
possibleTransformations: List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>,
obscure: Boolean,
onClickClose: () -> Unit
) {
val nameMultiplier = 3
val charaMultiplier = 4
val application = LocalContext.current.applicationContext as VBHelper
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 currentCharaPossibleTransformations = possibleTransformations.filter { it.fromCharaId == currentChara.id }
val romanNumeralsStage = when (currentChara.stage) {
1 -> "II"
@@ -222,22 +204,6 @@ fun DexCharaDetailsDialog(
}
}
Row {
if (currentCharaPossibleFusions.isNotEmpty()) {
Button(
onClick = {
showFusions = true
}
) {
Text("Fusions")
}
}
Spacer(
modifier = Modifier
.padding(4.dp)
)
Button(
onClick = onClickClose
) {
@@ -246,16 +212,4 @@ fun DexCharaDetailsDialog(
}
}
}
}
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")
}
}
}
}
}
@@ -1,5 +1,6 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
@@ -55,7 +56,6 @@ fun HomeScreen(
var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }
var betaWarning by rememberSaveable { mutableStateOf(true) }
var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) }
var collectedCurrency by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(storageRepository, activeMon, collectedItem) {
withContext(Dispatchers.IO) {
@@ -92,6 +92,7 @@ fun HomeScreen(
}
) { contentPadding ->
if (activeMon.value == null || (beData.value == null && vbData.value == null) || transformationHistory.value == null) {
Log.d("TetTet", "Something is null")
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
@@ -102,6 +103,7 @@ fun HomeScreen(
Text(text = "Nothing to see here")
}
} else {
Log.d("TetTet", "Something is not null")
if (activeMon.value!!.isBemCard) {
BEBEmHomeScreen(
activeMon = activeMon.value!!,
@@ -124,9 +126,8 @@ fun HomeScreen(
contentPadding = contentPadding,
specialMissions = vbSpecialMissions.value,
homeScreenController = homeScreenController,
onClickCollect = { item, currency ->
onClickCollect = { item ->
collectedItem = item
collectedCurrency = currency
}
)
}
@@ -136,10 +137,8 @@ fun HomeScreen(
if (collectedItem != null) {
ObtainedItemDialog(
obtainedItem = collectedItem!!,
obtainedCurrency = collectedCurrency!!,
onClickDismiss = {
collectedItem = null
collectedCurrency = null
}
)
}
@@ -5,5 +5,5 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface HomeScreenController {
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, 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.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.time.Instant
import kotlin.math.roundToInt
@@ -23,7 +22,6 @@ class HomeScreenControllerImpl(
val adventureCharacters = database
.adventureDao()
.getAdventureCharacters()
.first()
val finishedAdventureCharacters = adventureCharacters.filter { character ->
character.finishesAdventure <= currentTime
@@ -33,7 +31,7 @@ class HomeScreenControllerImpl(
}
}
override fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit) {
override fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?) -> Unit) {
componentActivity.lifecycleScope.launch {
database
.specialMissionDao()
@@ -43,7 +41,6 @@ class HomeScreenControllerImpl(
val randomItem = database
.itemDao()
.getAllItems()
.first()
.random()
val randomItemAmount = (Random.nextFloat() * 5).roundToInt()
@@ -65,13 +62,9 @@ class HomeScreenControllerImpl(
itemType = randomItem.itemType
)
val randomAmount = (2..6).random() * 1000
val currentCurrency = application.container.currencyRepository.currencyValue.first()
application.container.currencyRepository.setCurrencyValue(currentCurrency + randomAmount)
onCleared(purchasedItem, randomAmount)
onCleared(purchasedItem)
} else {
onCleared(null, null)
onCleared(null)
}
}
@@ -34,7 +34,7 @@ fun VBDiMHomeScreen(
homeScreenController: HomeScreenControllerImpl,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues,
onClickCollect: (ItemDtos.PurchasedItem?, Int?) -> Unit
onClickCollect: (ItemDtos.PurchasedItem?) -> Unit
) {
Column(
modifier = Modifier
@@ -24,7 +24,6 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@@ -36,7 +35,7 @@ fun ChooseCharacterScreen(
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db, )
val storageRepository = StorageRepository(application.container.db)
val characterList = remember {
mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList())
}
@@ -58,7 +57,7 @@ fun ChooseCharacterScreen(
characterList.value = storageRepository.getVBCharacters()
}
else -> {
characterList.value = storageRepository.getAllCharacters().first()
characterList.value = storageRepository.getAllCharacters()
}
}
}
@@ -1,164 +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.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 = "Costs ${item.price} credits",
modifier = Modifier
.fillMaxWidth()
)
}
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have ${item.quantity} of this item",
modifier = Modifier
.fillMaxWidth()
)
Row (
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
) {
if (onClickUse != null) {
Button(
onClick = onClickUse
) {
Text("Use item")
}
}
if (onClickPurchase != null) {
Button(
onClick = onClickPurchase
) {
Text("Purchase")
}
}
Spacer(modifier = Modifier.size(8.dp))
Button(
onClick = onClickCancel
) {
Text("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,23 +1,36 @@
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.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import 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.ui.theme.VBHelperTheme
@Composable
fun ItemElement(
item: ItemDtos.ItemsWithQuantities,
itemIcon: Int,
lengthIcon: Int,
modifier: Modifier = Modifier,
onClick: (() -> Unit) = { }
) {
@@ -28,7 +41,7 @@ fun ItemElement(
) {
Box(modifier = Modifier.fillMaxSize()) {
Icon(
painter = painterResource(id = getIconResource(item.itemIcon)),
painter = painterResource(id = itemIcon),
contentDescription = null,
modifier = Modifier
.size(96.dp)
@@ -36,14 +49,164 @@ fun ItemElement(
.padding(16.dp)
)
Icon(
painter = painterResource(id = getLengthResource(item.itemLength)),
painter = painterResource(id = lengthIcon),
contentDescription = null,
tint = MaterialTheme.colorScheme.surfaceTint,
modifier = Modifier
.size(48.dp)
.align(Alignment.TopStart)
.size(48.dp) // Set the size of the overlay image
.align(Alignment.TopStart) // Align to the top end (top-right corner)
.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
}
}
@@ -1,35 +1,25 @@
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.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
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.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun ItemsStore(
@@ -37,98 +27,45 @@ fun ItemsStore(
) {
val application = LocalContext.current.applicationContext as VBHelper
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) }
val currencyRepository = application.container.currencyRepository
val currentCurrency = currencyRepository.currencyValue.collectAsState(0)
LaunchedEffect(itemsRepository) {
withContext(Dispatchers.IO) {
myItems.value = itemsRepository.getAllItems()
}
}
val scope = rememberCoroutineScope()
if (myItems.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
if (myItems.value.isEmpty()) {
Text("No items")
}
} else {
Column() {
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text(
text = "${currentCurrency.value} credits",
modifier = Modifier
.padding(8.dp)
)
}
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
) {
items(myItems) { index ->
items(myItems.value) { index ->
ItemElement(
item = index,
itemIcon = getIconResource(index.itemIcon),
lengthIcon = getLengthResource(index.itemLength),
modifier = Modifier
.padding(8.dp),
onClick = {
selectedElementIndex = myItems.indexOf(index)
selectedElementIndex = myItems.value.indexOf(index)
}
)
}
}
}
}
if (selectedElementIndex != null) {
ItemDialog(
item = myItems[selectedElementIndex!!],
onClickPurchase = {
scope.launch {
Toast.makeText(
application.applicationContext,
purchaseItem(
application.container.db,
myItems[selectedElementIndex!!],
currencyRepository
),
Toast.LENGTH_SHORT
).show(
)
}
},
name = myItems.value[selectedElementIndex!!].name,
description = myItems.value[selectedElementIndex!!].description,
itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon),
lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength),
amount = myItems.value[selectedElementIndex!!].quantity,
onClickUse = { },
onClickCancel = { selectedElementIndex = null }
)
}
}
suspend fun purchaseItem(
db: AppDatabase,
item: ItemDtos.ItemsWithQuantities,
currencyRepository: CurrencyRepository
): String {
if (currencyRepository.currencyValue.first() < item.price) {
return "Not enough credits"
} else {
db
.itemDao()
.purchaseItem(
item.id,
1
)
currencyRepository
.setCurrencyValue(
currencyRepository.currencyValue.first() - item.price
)
return "Purchase successful!"
}
}
@@ -9,7 +9,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -20,8 +20,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun MyItems(
@@ -29,11 +32,16 @@ fun MyItems(
) {
val application = LocalContext.current.applicationContext as VBHelper
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) }
if (myItems.isEmpty()) {
LaunchedEffect(itemsRepository) {
withContext(Dispatchers.IO) {
myItems.value = itemsRepository.getUserItems()
}
}
if (myItems.value.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -46,13 +54,14 @@ fun MyItems(
columns = GridCells.Fixed(3),
modifier = Modifier
) {
items(myItems) { index ->
items(myItems.value) { index ->
ItemElement(
item = index,
itemIcon = getIconResource(index.itemIcon),
lengthIcon = getLengthResource(index.itemLength),
modifier = Modifier
.padding(8.dp),
onClick = {
selectedElementIndex = myItems.indexOf(index)
selectedElementIndex = myItems.value.indexOf(index)
}
)
}
@@ -60,7 +69,11 @@ fun MyItems(
if (selectedElementIndex != null) {
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 = {
navController
.navigate(
@@ -68,7 +81,7 @@ fun MyItems(
.ApplyItem.route
.replace(
"{itemId}",
myItems[selectedElementIndex!!].id.toString()
myItems.value[selectedElementIndex!!].id.toString()
)
)
selectedElementIndex = null
@@ -23,7 +23,6 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
@Composable
fun ObtainedItemDialog(
obtainedItem: ItemDtos.PurchasedItem,
obtainedCurrency: Int,
onClickDismiss: () -> Unit
) {
Dialog(
@@ -85,16 +84,7 @@ fun ObtainedItemDialog(
text = "You have obtained ${obtainedItem.itemAmount} of this item",
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp)
)
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You also got $obtainedCurrency credits",
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp)
.padding(5.dp)
)
Button(
onClick = onClickDismiss,
@@ -1,78 +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.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
@Composable
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
navController: NavController
) {
Scaffold(
topBar = {
TopBanner(
text = "Scan a Vital Bracelet",
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
ScanButton(
text = "Vital Bracelet to App",
disabled = onClickRead == null,
onClick = onClickRead?: { },
)
Spacer(modifier = Modifier.height(16.dp))
ScanButton(
text = "App to Vital Bracelet",
disabled = onClickWrite == null,
onClick = onClickWrite?: { },
)
}
}
}
@Composable
fun ScanButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
disabled: Boolean = false,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = !disabled,
) {
Text(
text = text,
fontSize = 16.sp,
modifier = Modifier
.padding(4.dp)
)
}
}
@@ -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.Column
@@ -14,16 +14,13 @@ import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
@Composable
fun ActionScreen(
fun ReadingCharacterScreen(
topBannerText: String,
onClickCancel: () -> Unit,
) {
Scaffold (
topBar = {
TopBanner(
text = topBannerText,
onBackClick = onClickCancel
)
TopBanner(topBannerText)
}
) { innerPadding ->
Column (
@@ -1,24 +1,38 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.ReadingScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.WritingScreen
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets
@@ -41,6 +55,8 @@ fun ScanScreen(
val storageRepository = StorageRepository(application.container.db)
var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) }
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
val context = LocalContext.current
LaunchedEffect(storageRepository) {
@@ -57,33 +73,143 @@ fun ScanScreen(
}
}
var writingScreen 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) {
WritingScreen(
scanScreenController = scanScreenController,
nfcCharacter = nfcCharacter!!,
characterId = characterId,
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 = {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
isDoneReadingCharacter = true
},
onCancel = {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
} else if (readingScreen) {
ReadingScreen(
scanScreenController = scanScreenController,
onCancel = {
readingScreen = false
navController.navigate(NavigationItems.Home.route)
},
}
}
)
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
DisposableEffect(writingScreen, isDoneSendingCard) {
if (writingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
}
)
}
if (secrets != null && nfcCharacter != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
onDispose {
if(writingScreen) {
scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false
navController.navigate(NavigationItems.Home.route)
} else if (isDoneSendingCard && isDoneWritingCharacter) {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
LaunchedEffect(storageRepository) {
withContext(Dispatchers.IO) {
storageRepository
.deleteCharacter(characterId!!)
}
}
}
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 {
@@ -121,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)
@Composable
fun ScanScreenPreview() {
@@ -1,11 +1,13 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.util.Log
import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
@@ -150,13 +152,15 @@ class FromNfcConverter (
nfcCharacter: NfcCharacter,
cardData: Card
) {
val currentCardProgress = CardProgress(
cardId = cardData.id,
currentStage = nfcCharacter.nextAdventureMissionStage.toInt(),
unlocked = nfcCharacter.nextAdventureMissionStage.toInt() > cardData.stageCount
)
database
.cardProgressDao()
.updateCardProgress(
currentStage = nfcCharacter.nextAdventureMissionStage.toInt(),
cardId = cardData.id,
unlocked = nfcCharacter.nextAdventureMissionStage.toInt() > cardData.stageCount,
)
.updateDimProgress(currentCardProgress)
}
@@ -248,11 +252,10 @@ class FromNfcConverter (
) {
val vitalsHistoryWatch = nfcCharacter.vitalHistory
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(
charId = characterId,
year = year,
year = historyElement.year.toInt(),
month = historyElement.month.toInt(),
day = historyElement.day.toInt(),
vitalPoints = historyElement.vitalsGained.toInt()
@@ -14,7 +14,6 @@ import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.flow.first
import java.util.Date
class ToNfcConverter(
@@ -39,10 +38,14 @@ class ToNfcConverter(
.characterDao()
.getCharacterInfo(userCharacter.charId)
val currentCardStage = database
.cardProgressDao()
.getCardProgress(characterInfo.cardId)
return if (userCharacter.characterType == DeviceType.BEDevice)
nfcToBENfc(characterId, characterInfo, userCharacter)
nfcToBENfc(characterId, characterInfo, currentCardStage, userCharacter)
else
nfcToVBNfc(characterId, characterInfo, userCharacter)
nfcToVBNfc(characterId, characterInfo, currentCardStage, userCharacter)
}
@@ -50,6 +53,7 @@ class ToNfcConverter(
private suspend fun nfcToVBNfc(
characterId: Long,
characterInfo: CharacterDtos.CardCharacterInfo,
currentCardStage: Int,
userCharacter: UserCharacter
): VBNfcCharacter {
val vbData = database
@@ -66,7 +70,7 @@ class ToNfcConverter(
stage = characterInfo.stage.toByte(),
attribute = characterInfo.attribute,
ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = characterInfo.currentStage.toByte(),
nextAdventureMissionStage = currentCardStage.toByte(),
mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(),
transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(),
@@ -97,7 +101,6 @@ class ToNfcConverter(
val cardData = database
.cardDao()
.getCardByCharacterId(userCharacter.id)
.first()
val appReserved = Array<UShort>(3) {
0u
@@ -170,6 +173,7 @@ class ToNfcConverter(
private suspend fun nfcToBENfc(
characterId: Long,
characterInfo: CharacterDtos.CardCharacterInfo,
currentCardStage: Int,
userCharacter: UserCharacter
): BENfcCharacter {
val beData = database
@@ -184,7 +188,7 @@ class ToNfcConverter(
stage = characterInfo.stage.toByte(),
attribute = characterInfo.attribute,
ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = characterInfo.currentStage.toByte(),
nextAdventureMissionStage = currentCardStage.toByte(),
mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(),
itemEffectMentalStateValue = beData.itemEffectMentalStateValue.toByte(),
@@ -1,59 +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.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
@Composable
fun ReadCharacterScreen(
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
Scaffold(
topBar = {
TopBanner(
text = "Read character",
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Text(
text = "Prepare your device!",
textAlign = TextAlign.Center
)
Text(
text = "Go to connect and when ready press confirm!",
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.padding(8.dp)
)
Button(
onClick = onClickConfirm,
) {
Text("Confirm")
}
}
}
}
@@ -1,109 +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 com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
@Composable
fun ReadingScreen(
scanScreenController: ScanScreenController,
onCancel: () -> Unit,
onComplete: () -> Unit
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
var readingScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var cardSelectScreen by remember { mutableStateOf(false) }
DisposableEffect(readingScreen) {
if(readingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object: ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
}
)
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false
onComplete()
}
if (!readingScreen) {
ReadCharacterScreen(
onClickConfirm = {
readingScreen = true
},
onClickCancel = {
onCancel()
}
)
}
if (readingScreen) {
ActionScreen("Reading character") {
readingScreen = false
scanScreenController.cancelRead()
onCancel()
}
} else if (cardSelectScreen) {
ChooseCard(
cards = cardsRead!!,
onCardSelected = { card ->
cardSelectScreen = false
scanScreenController.flushCharacter(card.id)
}
)
}
}
@@ -1,132 +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.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
@Composable
fun WriteCardScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = "Writing card details",
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text("Get your device Ready!")
Text("You will need ${cardDetails.name} card!")
}
}
}
Button(
onClick = onClickConfirm,
) {
Text("Confirm")
}
}
}
}
@@ -1,135 +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.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
@Composable
fun WriteCharacterScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = "Writing character",
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text("Card installed successfully!!")
Text("Wait until your device is ready, then tap 'Confirm'")
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = onClickConfirm,
) {
Text("Confirm")
}
}
}
}
@@ -1,140 +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
@Composable
fun WritingScreen(
scanScreenController: ScanScreenController,
characterId: Long,
nfcCharacter: NfcCharacter,
onComplete: () -> Unit,
onCancel: () -> Unit,
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
var writing by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var writingConfirmScreen by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
DisposableEffect(writing) {
if (writing) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
)
if (secrets != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
onDispose {
if (writing) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (!writingScreen) {
writing = false
WriteCardScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingScreen = true
}
)
} else if (!isDoneSendingCard) {
writing = true
ActionScreen("Sending card") {
scanScreenController.cancelRead()
onCancel()
}
} else if (!writingConfirmScreen) {
writing = false
WriteCharacterScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingConfirmScreen = true
}
)
} else if (!isDoneWritingCharacter) {
writing = true
ActionScreen("Writing character") {
isDoneSendingCard = false
scanScreenController.cancelRead()
onCancel()
}
}
var completedWriting by remember { mutableStateOf(false) }
LaunchedEffect(isDoneSendingCard, isDoneWritingCharacter) {
withContext(Dispatchers.IO) {
if (isDoneSendingCard && isDoneWritingCharacter) {
storageRepository
.deleteCharacter(characterId)
completedWriting = true
}
}
}
if (completedWriting) {
onComplete()
}
}
@@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems
@Composable
fun CreditsScreen(
@@ -19,6 +20,9 @@ fun CreditsScreen(
text = "Credits",
onBackClick = {
navController.popBackStack()
},
onGearClick = {
navController.navigate(NavigationItems.Viewer.route)
}
)
},
@@ -1,7 +1,5 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -15,7 +13,6 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -28,8 +25,6 @@ fun SettingsScreen(
navController: NavController,
settingsScreenController: SettingsScreenControllerImpl,
) {
val context = LocalContext.current
Scaffold (
topBar = {
TopBanner(
@@ -49,22 +44,19 @@ fun SettingsScreen(
.verticalScroll(rememberScrollState())
) {
SettingsSection("NFC Communication")
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arena 2.1.0 APK") {
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
settingsScreenController.onClickImportApk()
}
SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import card", description = "Import DiM/BEm card file") {
settingsScreenController.onClickImportCard()
}
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
SettingsSection("About and credits")
SettingsEntry(title = "Credits", description = "Credits") {
navController.navigate(NavigationItems.Credits.route)
}
SettingsEntry(title = "About", description = "About") {
val browserIntent = Intent(
Intent.ACTION_VIEW, Uri.parse("https://github.com/nacabaro/vbhelper/"))
context.startActivity(browserIntent)
}
SettingsEntry(title = "About", description = "About") { }
SettingsSection("Data management")
SettingsEntry(title = "Export data", description = "Export application database") {
settingsScreenController.onClickOpenDirectory()
@@ -3,24 +3,38 @@ package com.github.nacabaro.vbhelper.screens.settingsScreen
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import android.net.Uri
import android.provider.OpenableColumns
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
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.di.VBHelper
import com.github.nacabaro.vbhelper.screens.settingsScreen.controllers.CardImportController
import com.github.nacabaro.vbhelper.screens.settingsScreen.controllers.DatabaseManagementController
import com.github.nacabaro.vbhelper.domain.characters.Sprite
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.SecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsRepository
import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers
import java.io.File
import java.io.InputStream
import java.io.OutputStream
class SettingsScreenControllerImpl(
private val context: ComponentActivity,
): SettingsScreenController {
private val roomDbName = "internalDb"
private val filePickerLauncher: ActivityResultLauncher<String>
private val filePickerOpenerLauncher: ActivityResultLauncher<Array<String>>
private val filePickerApk: ActivityResultLauncher<Array<String>>
@@ -29,17 +43,13 @@ class SettingsScreenControllerImpl(
private val application = context.applicationContext as VBHelper
private val secretsRepository: SecretsRepository = application.container.dataStoreSecretsRepository
private val database: AppDatabase = application.container.db
private val databaseManagementController = DatabaseManagementController(
componentActivity = context,
application = application
)
init {
filePickerLauncher = context.registerForActivityResult(
ActivityResultContracts.CreateDocument("application/octet-stream")
) { uri ->
if (uri != null) {
databaseManagementController.exportDatabase(uri)
exportDatabase(uri)
} else {
context.runOnUiThread {
Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT)
@@ -52,7 +62,7 @@ class SettingsScreenControllerImpl(
ActivityResultContracts.OpenDocument()
) { uri ->
if (uri != null) {
databaseManagementController.importDatabase(uri)
importDatabase(uri)
} else {
context.runOnUiThread {
Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show()
@@ -101,14 +111,324 @@ class SettingsScreenControllerImpl(
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) {
context.lifecycleScope.launch(Dispatchers.IO) {
val contentResolver = context.contentResolver
val inputStream = contentResolver.openInputStream(uri)
inputStream.use { fileReader ->
val cardImportController = CardImportController(database)
cardImportController.importCard(fileReader)
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)
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()
@@ -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) {
context.lifecycleScope.launch(Dispatchers.IO) {
context.contentResolver.openInputStream(uri).use {
@@ -1,320 +0,0 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen.controllers
import android.util.Log
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<*, *, *, *, *, *>
) {
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 {
Log.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,115 +0,0 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen.controllers
import android.net.Uri
import android.provider.OpenableColumns
import android.util.Log
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) {
Log.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) {
Log.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,9 +1,9 @@
package com.github.nacabaro.vbhelper.screens.spriteViewer
import android.graphics.Bitmap
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.domain.card.CardBackground
interface SpriteViewerController {
suspend fun getAllSprites(): List<Sprite>
fun convertToBitmap(sprites: List<Sprite>): List<Bitmap>
suspend fun getAllSprites(): List<CardBackground>
fun convertToBitmap(sprites: List<CardBackground>): List<Bitmap>
}
@@ -1,65 +1,29 @@
package com.github.nacabaro.vbhelper.screens.spriteViewer
import android.graphics.Bitmap
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.asImageBitmap
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import java.nio.ByteBuffer
import androidx.core.graphics.createBitmap
import com.github.nacabaro.vbhelper.domain.card.CardBackground
class SpriteViewerControllerImpl(
private val context: ComponentActivity
) : SpriteViewerController {
override suspend fun getAllSprites(): List<Sprite> {
override suspend fun getAllSprites(): List<CardBackground> {
val applicationContext = context.applicationContext as VBHelper
val db = applicationContext.container.db
val sprites = db.spriteDao().getAllSprites()
val sprites = db.cardBackgroundDao().getBackgrounds()
return sprites
}
// 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>()
for (sprite in sprites) {
Log.d("SpriteViewer", "sprite: $sprite")
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle1))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle2))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk1))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk2))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun1))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun2))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain1))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain2))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteHappy))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteSleep))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteAttack))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteDodge))
bitmapList.add(createBitmap(sprite.backgroundWidth, sprite.backgroundHeight, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.background))
})
}
@@ -38,7 +38,6 @@ import kotlinx.coroutines.launch
fun StorageDialog(
characterId: Long,
onDismissRequest: () -> Unit,
onClickDelete: () -> Unit,
onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit,
onClickSendToAdventure: (time: Long) -> Unit
@@ -142,13 +141,6 @@ fun StorageDialog(
) {
Text(text = "Send on adventure")
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = onClickDelete
) {
Text(text = "Delete character")
}
Button(
modifier = Modifier
.fillMaxWidth(),
@@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
@@ -12,15 +11,14 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
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.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -30,10 +28,12 @@ import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.launch
@Composable
@@ -42,12 +42,19 @@ fun StorageScreen(
storageScreenController: StorageScreenControllerImpl,
adventureScreenController: AdventureScreenControllerImpl
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
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) }
LaunchedEffect(storageRepository, selectedCharacter) {
coroutineScope.launch {
val characterList = storageRepository.getAllCharacters()
monList.value = characterList
}
}
Scaffold (
topBar = {
TopBanner(
@@ -58,7 +65,7 @@ fun StorageScreen(
)
}
) { contentPadding ->
if (characterList.isEmpty()) {
if (monList.value.isEmpty()) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
@@ -79,7 +86,7 @@ fun StorageScreen(
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding())
) {
items(characterList) { index ->
items(monList.value) { index ->
CharacterEntry(
icon = BitmapData(
bitmap = index.spriteIdle,
@@ -100,16 +107,6 @@ fun StorageScreen(
)
}
},
cardColors = if (index.active) {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primary
)
} else {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
)
}
)
}
}
@@ -141,16 +138,6 @@ fun StorageScreen(
timeInMinutes = time
)
selectedCharacter = null
},
onClickDelete = {
storageScreenController
.deleteCharacter(
characterId = selectedCharacter!!,
onCompletion = {
Log.d("StorageScreen", "Character deleted")
}
)
selectedCharacter = null
}
)
}
@@ -2,5 +2,4 @@ package com.github.nacabaro.vbhelper.screens.storageScreen
interface StorageScreenController {
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,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.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
class DexRepository (
private val db: AppDatabase
) {
fun getAllDims(): Flow<List<CardDtos.CardProgress>> {
suspend fun getAllDims(): List<CardDtos.CardProgress> {
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)
}
fun getCharacterPossibleTransformations(characterId: Long): Flow<List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>> {
return db.characterDao().getEvolutionRequirementsForCard(characterId)
}
fun getCharacterPossibleFusions(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>> {
return db.cardFusionsDao().getFusionsForCharacter(characterId)
suspend fun getCardPossibleTransformations(cardId: Long): List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained> {
return db.characterDao().getEvolutionRequirementsForCard(cardId)
}
}
@@ -1,17 +1,17 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.items.Items
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
class ItemsRepository(
private val db: AppDatabase
) {
fun getAllItems(): Flow<List<ItemDtos.ItemsWithQuantities>> {
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities> {
return db.itemDao().getAllItems()
}
fun getUserItems(): Flow<List<ItemDtos.ItemsWithQuantities>> {
suspend fun getUserItems(): List<ItemDtos.ItemsWithQuantities> {
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.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
class StorageRepository (
private val db: AppDatabase
) {
fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>> {
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites> {
return db.userCharacterDao().getAllCharacters()
}
@@ -47,7 +46,7 @@ class StorageRepository (
return db.userCharacterDao().deleteCharacterById(id)
}
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>> {
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> {
return db.adventureDao().getAdventureCharacters()
}
+1 -1
View File
@@ -1,5 +1,5 @@
[versions]
agp = "8.13.1"
agp = "8.12.3"
datastore = "1.1.2"
kotlin = "2.0.0"
coreKtx = "1.15.0"