Compare commits

...

28 Commits

Author SHA1 Message Date
37076a9bf0 VERSION UP 2026-01-25 20:00:55 +01:00
8c99046edb
Merge pull request #47 from nacabaro/specialMissions/fix
Fix Special Missions a bit more
2026-01-25 19:58:39 +01:00
c990a495b7 Fix Special Missions a bit more
Now they should behave a bit better, and it will not allow the watch to have multiple missions of the same type, plus you can now delete special missions.
2026-01-25 19:58:03 +01:00
0e61723db1
Merge pull request #46 from nacabaro/ui/cardIcon
Card icon in home screen
2026-01-25 17:53:30 +01:00
8815907563 Card icon in home screen
Together with character sprite
2026-01-25 17:35:54 +01:00
6cce33c5f6
Merge pull request #44 from jeffersoncarlospedroso/feature/add-multilanguage-support
Add multilanguage support (i18n) initial implementation
2025-12-14 12:31:27 +01:00
jeffersoncarlospedroso
16fbfae0c2 feat(i18n): continue translation work 2025-12-11 22:38:43 +00:00
jeffersoncarlospedroso
0d174f1550 Add multilanguage support (i18n) initial implementation 2025-12-10 23:06:37 +00:00
489e27b038 More things
- Active characters display different
- Adventure missions award credits (same as special missions)
- Clicking on about will open the github page
- Corrected from arean to arena
- Removed rename from the settings (it was not used)
2025-11-16 21:59:08 +00:00
6be167bbed Might be incomplete uhhh 2025-11-16 21:13:28 +01:00
61dfc2ce0d
Merge pull request #43 from nacabaro/screen/upload_screen
Other few things
2025-11-16 01:35:31 +01:00
dce186737d Other few things
- I changed more things to flows from the database
- Cleaned up the logic coming from the scan screen
- Added a delete button to a character. CAREFUL, IT HAS NO CONFIRMATION YET!
- Fixed a few things, now scanning is more stable and will fix the second whoops thing.
- Quick patch, should improve stability when writing to the watch
2025-11-16 01:34:31 +01:00
cf272e8030
Merge pull request #42 from nacabaro/dex/possible_fusions
Few things
2025-11-15 20:12:36 +01:00
ffa6958a89 Few things
- Finished working with the card fusions
- Added the GUI part to access the data
- Cleaned up a bit of the code, separated a few things from the SettingsScreenControllerImpl.kt into separate classes.
- Removed a few LaunchedEffect in exchange of Flows (UI now updates automatically when an action happens)
2025-11-15 20:10:17 +01:00
1d9879fb50
Merge pull request #41 from nacabaro/store/updated_store
Updated the store
2025-11-15 10:23:07 +01:00
3fa072ce1e Updated the store
- Items can now be purchased
- Changed settings to make use of flows/stateflows from the database, which update the screen in real time after any changes.
- Separated functions from files
2025-11-13 23:52:14 +01:00
69245be8dd Updated README.md 2025-11-03 13:20:16 +01:00
Nacho
73c393df64 Fixed bug syncing watch adventure missions progress with the app 2025-09-14 00:34:05 +02:00
nacabaro
0b3ce486cb
Merge pull request #39 from nacabaro/card/import_more
Adventure Missions progress
2025-09-10 02:10:22 +02:00
Nacho
7160eb792a Adventure Missions progress
- Viewing the adventure missions completed in a card is now possible.
2025-09-10 02:09:15 +02:00
Nacho
cb9fe8e716 Merge remote-tracking branch 'origin/main' 2025-09-05 02:22:09 +02:00
Nacho
540766eefb VERSION UP 2025-09-05 02:21:47 +02:00
nacabaro
af2c852bac
Merge pull request #38 from nacabaro/vb/special_missions
Small fix related to special missions
2025-09-05 02:19:36 +02:00
Nacho
8dc5bbbdde Small fix related to special missions
- Now you can clear failed special missions. When a special mission fails, you will not get an item after clearing it.

- Huge note: I have not tested this fix, but it should work... At least that's what my intuition is telling me!

Jokes aside, this should work, but the home screen will not update. I should update this to make use of StateFlows and Flows to keep the home screen updated at all times.
2025-09-05 02:19:09 +02:00
nacabaro
aac05a3b1c
Merge pull request #37 from nacabaro/card/import_more
Agarrate, que vienen curvas
2025-09-05 02:10:05 +02:00
Nacho
b827fdccbe Agarrate, que vienen curvas
- Importing more data from the card, this time it's to enhance the functionality of the dex. Things that I'm importing:
  - Tropies
  - Vitals
  - WinRate
  - Battles needed
  - Adventure level cleared
- This commit also includes displaying the data, modifying the relational model and modifying the underlying import functions.

- I've also changed the name from Character to CharacterData, since there is another class that is also called Character. I wanted to move it into the card package, since it's more related to Cards, while character is more designed for user raised characters.

- Another small addition was the ability to construct ImageBitmaps from the BitmapData.kt class. reduces code and makes it more simple, will refactor the old code to use this later on.

- Things I might do
  - Move out of the SettingsScreenControllerImpl.kt file all the card import stuff, since it's slowly growing a lot, and it could benefit from some independence
2025-09-05 02:08:44 +02:00
Nacho
2586190e5c Update libraries 2025-09-02 18:01:11 +02:00
Nacho
1a6753d2aa Fixed date issues with TransformationHistory 2025-09-02 04:54:08 +02:00
92 changed files with 4074 additions and 1120 deletions

101
README.md
View File

@ -1,38 +1,69 @@
# VBHelper # VBHelper
## Developer Setup Application to interact with the Vital series, VB, VH, VBC and VBBE.
1. Clone vb-nfc-reader (https://github.com/cfogrady/lib-vb-nfc) ## Current state of the project
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) Right now the project is still under development, and until further notice, any database updates will result in having to erase application data.
4. Run publishToMavenLocal gradle task in the vb-dim-reader project.
5. Create res/values/keys.xml within the app module. This document will be updated once the application does not need any more database resets.
6. Populate with:
``` ## Features
<?xml version="1.0" encoding="utf-8"?>
<resources> As of now, the project allows you to read characters, view characters stats, and send them back to your watch.
<string name="password1">beHmacKey1</string>
<string name="password2">beHmacKey2</string> You can also apply items to the characters read, such as special missions, or change timers, and store characters in the storage section.
<string name="decryptionKey">aesKey</string>
<integer-array name="substitutionArray"> 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.
<item>0</item>
<item>1</item> App also comes with a dex that will update every time a new character is added, and allows you to see evolution requirements and current adventure stage in the watch.
<item>2</item>
<item>3</item> ## How to set up
<item>4</item>
<item>5</item> 1. Download the latest version for VB Arena APK from a trustworthy source. If your download is a standalone APK, continue to step 2. Otherwise, if your download is an XAPK, do the following:
<item>6</item>
<item>7</item> 1. Using your phone file manager, rename the XAPK file to ZIP, and extract its contents. You can also do this with any other device, such as Windows, macOS and Linux.
<item>8</item>
<item>9</item> 2. Once the files are extracted, look for an APK called `com.bandai.vitalbraceletarena.apk`. Copy it somewhere else, you will need it.
<item>10</item>
<item>11</item> 2. Install an APK release for VB Arena. You will find the releases [here](http://github.com/nacabaro/vbhelper/releases). Download the latest release and install its APK.
<item>12</item>
<item>13</item> Note, in the current stage of the project, you will have to delete the old application from your device. If the app keeps crashing after installing, clear application data and storage.
<item>14</item>
<item>15</item> 3. Import secrets in the app. These secrets will allow the app to talk to the watch. On the main screen, click on the gear icon, then `Import secrets`.
</integer-array>
</resources> You will be prompted to choose a file. Choose the APK file that was previously obtained.
```
7. Replace the values in the keys.xml file with those extracted from the original app. 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.
8. Run
1. Using your own DiM/BEm cards, dump the cards to your device. You can get an in-depth tutorial in [here](http://mrblinky.net/digimon/vb/dimcardtool/dimcardtool.html). You can download the dump tool from [here](http://mrblinky.net/digimon/vb/dimcardtool/)
2. Once installed the tool and drivers, open the tool, connect your DiM/BEm reader hardware to yout computer and click on Read card.
3. Transfer the resulting file to your mobile device. You can put them anywhere, as long as they are accessible. My recommendation is to put them under a folder called `Cards` in your `Internal storage` or `SD Card`
4. In the app, click on import card. Next choose the BIN file corresponding to the card you want to import.
**Note: if you do not import the card, whenever you attempt to read a character from th watch, the character you read will get deleted.**
5. App will now be ready to be used.
## Planned features
- Online battles, undegoing development by `lightheel`.
- VitalWear compatibility, undergoing development by `cfogrady`.
- Support for multiple languages, not yet started.
- Database backup/restore.
## Credits
- `cyanic` for helping us understand more about the VB connection protocol.
- `cfogrady` for making both [`VB-DIM-Reader`](https://github.com/cfogrady/VB-DIM-Reader) and [`lib-vb-nfc`](https://github.com/cfogrady/lib-vb-nfc)
- `lightheel` for working on the online component in the application, both server and battle client.
- `shvstrz` for the app icon.

View File

@ -15,7 +15,7 @@ android {
minSdk = 28 minSdk = 28
targetSdk = 35 targetSdk = 35
versionCode = 1 versionCode = 1
versionName = "Alpha 0.5.1" versionName = "Alpha 0.6.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@ -91,4 +91,5 @@ dependencies {
implementation("com.google.android.material:material:1.2.0") implementation("com.google.android.material:material:1.2.0")
implementation(libs.protobuf.javalite) implementation(libs.protobuf.javalite)
implementation("androidx.compose.material:material") implementation("androidx.compose.material:material")
implementation("androidx.datastore:datastore-preferences:1.1.7")
} }

View File

@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -35,20 +36,27 @@ import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.R import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.utils.getObscuredBitmap import com.github.nacabaro.vbhelper.utils.getObscuredBitmap
import androidx.compose.ui.res.stringResource
@Composable @Composable
fun CharacterEntry( fun CharacterEntry(
icon: BitmapData, icon: BitmapData,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
cardIcon: BitmapData? = null,
obscure: Boolean = false, obscure: Boolean = false,
disabled: Boolean = false, disabled: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium, shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 4, multiplier: Int = 4,
cardColors: CardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
),
onClick: () -> Unit = { } onClick: () -> Unit = { }
) { ) {
val bitmap = remember (icon.bitmap) { val bitmap = remember (icon.bitmap) {
if(obscure) icon.getObscuredBitmap() else icon.getBitmap() if(obscure) icon.getObscuredBitmap() else icon.getBitmap()
} }
val iconSizeMultiplier = 3
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() } val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (icon.width * multiplier / density).dp val dpSize = (icon.width * multiplier / density).dp
@ -61,7 +69,8 @@ fun CharacterEntry(
}, },
modifier = modifier modifier = modifier
.aspectRatio(1f) .aspectRatio(1f)
.padding(8.dp) .padding(8.dp),
colors = cardColors
) { ) {
Box( Box(
contentAlignment = Alignment.BottomCenter, contentAlignment = Alignment.BottomCenter,
@ -79,7 +88,24 @@ fun CharacterEntry(
}, },
modifier = Modifier modifier = Modifier
.size(dpSize) .size(dpSize)
.align(Alignment.BottomCenter)
) )
if (cardIcon != null) {
val bitmap = remember (icon.bitmap) { cardIcon.getBitmap() }
val iconBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val dpSize = (icon.width * iconSizeMultiplier /density).dp
Image(
bitmap = iconBitmap,
contentDescription = "Card icon",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
.align(Alignment.BottomEnd)
.padding(8.dp)
)
}
} }
} }
} }
@ -126,14 +152,27 @@ fun ItemDisplay(
fun SpecialMissionsEntry( fun SpecialMissionsEntry(
specialMission: SpecialMissions, specialMission: SpecialMissions,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClickCard: () -> Unit = { }, onClickMission: (Long) -> Unit = { },
onClickCollect: (Long) -> Unit = { }
) { ) {
val textValue = when (specialMission.missionType) { val textValue = when (specialMission.missionType) {
SpecialMission.Type.NONE -> "No mission selected" SpecialMission.Type.NONE -> stringResource(R.string.special_mission_none)
SpecialMission.Type.STEPS -> "Walk ${specialMission.goal} steps" SpecialMission.Type.STEPS -> stringResource(
SpecialMission.Type.BATTLES -> "Battle ${specialMission.goal} times" R.string.special_mission_steps,
SpecialMission.Type.WINS -> "Win ${specialMission.goal} battles" specialMission.goal
SpecialMission.Type.VITALS -> "Earn ${specialMission.goal} vitals" )
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles,
specialMission.goal
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins,
specialMission.goal
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals,
specialMission.goal
)
} }
val progress = if (specialMission.status == SpecialMission.Status.COMPLETED) { val progress = if (specialMission.status == SpecialMission.Status.COMPLETED) {
@ -144,10 +183,22 @@ fun SpecialMissionsEntry(
val completion = when (specialMission.missionType) { val completion = when (specialMission.missionType) {
SpecialMission.Type.NONE -> "" SpecialMission.Type.NONE -> ""
SpecialMission.Type.STEPS -> "Walked $progress steps" SpecialMission.Type.STEPS -> stringResource(
SpecialMission.Type.BATTLES -> "Battled $progress times" R.string.special_mission_steps_progress,
SpecialMission.Type.WINS -> "Won $progress battles" progress
SpecialMission.Type.VITALS -> "Earned $progress vitals" )
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles_progress,
progress
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins_progress,
progress
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals_progress,
progress
)
} }
val icon = when (specialMission.missionType) { val icon = when (specialMission.missionType) {
@ -170,9 +221,11 @@ fun SpecialMissionsEntry(
modifier = modifier, modifier = modifier,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
onClick = if (specialMission.status == SpecialMission.Status.COMPLETED) { onClick = if (specialMission.status == SpecialMission.Status.COMPLETED) {
onClickCard { onClickCollect(specialMission.id) }
} else if (specialMission.status == SpecialMission.Status.UNAVAILABLE) {
{ }
} else { } else {
{ } { onClickMission(specialMission.id) }
}, },
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = color containerColor = color
@ -188,7 +241,7 @@ fun SpecialMissionsEntry(
) { ) {
Icon( Icon(
painter = painterResource(icon), painter = painterResource(icon),
contentDescription = "Vitals", contentDescription = stringResource(R.string.special_mission_icon_content_description),
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.padding(16.dp) .padding(16.dp)

View File

@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
@ -18,7 +19,8 @@ interface AdventureDao {
""") """)
fun getAdventureCount(): Int fun getAdventureCount(): Int
@Query(""" @Query(
"""
SELECT SELECT
uc.*, uc.*,
c.stage, c.stage,
@ -30,12 +32,13 @@ interface AdventureDao {
a.finishesAdventure AS finishesAdventure, a.finishesAdventure AS finishesAdventure,
a.originalDuration AS originalTimeInMinutes a.originalDuration AS originalTimeInMinutes
FROM UserCharacter uc FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Sprite s ON s.id = c.spriteId JOIN Sprite s ON s.id = c.spriteId
JOIN Card d ON c.dimId = d.id JOIN Card d ON c.cardId = d.id
JOIN Adventure a ON uc.id = a.characterId JOIN Adventure a ON uc.id = a.characterId
""") """
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> )
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>>
@Query(""" @Query("""
DELETE FROM Adventure DELETE FROM Adventure

View File

@ -0,0 +1,59 @@
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 {
@Query("""
INSERT INTO
CardAdventure (cardId, characterId, steps, bossAp, bossHp, bossDp, bossBp)
SELECT
:cardId,
cc.id,
:steps,
:bossAp,
:bossHp,
:bossDp,
:bossBp
FROM
CardCharacter cc
WHERE
cc.charaIndex = :characterId AND
cc.cardId = :cardId
""")
suspend fun insertNewAdventure(
cardId: Long,
characterId: Int,
steps: Int,
bossAp: Int,
bossHp: Int,
bossDp: Int,
bossBp: Int?
)
@Query("""
SELECT
cc.nameSprite as characterName,
cc.nameWidth as characterNameWidth,
cc.nameHeight as characterNameHeight,
s.spriteIdle1 as characterIdleSprite,
s.width as characterIdleSpriteWidth,
s.height as characterIdleSpriteHeight,
ca.bossAp as characterAp,
ca.bossBp as characterBp,
ca.bossDp as characterDp,
ca.bossHp as characterHp,
ca.steps as steps
FROM CardCharacter cc
JOIN Sprite s ON cc.spriteId = s.id
JOIN CardAdventure ca ON cc.id = ca.characterId
WHERE
cc.cardId = :cardId
""")
fun getAdventureForCard(
cardId: Long
): Flow<List<CardDtos.CardAdventureWithSprites>>
}

View File

@ -5,6 +5,8 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface CardDao { interface CardDao {
@ -17,18 +19,31 @@ interface CardDao {
@Query("SELECT * FROM Card WHERE id = :id") @Query("SELECT * FROM Card WHERE id = :id")
fun getCardById(id: Long): Card? fun getCardById(id: Long): Card?
@Query(""" @Query(
"""
SELECT ca.* SELECT ca.*
FROM Card ca FROM Card ca
JOIN Character ch ON ca.id = ch.dimId JOIN CardCharacter ch ON ca.id = ch.cardId
JOIN UserCharacter uc ON ch.id = uc.charId JOIN UserCharacter uc ON ch.id = uc.charId
WHERE uc.id = :id WHERE uc.id = :id
""") """
suspend fun getCardByCharacterId(id: Long): Card )
fun getCardByCharacterId(id: Long): Flow<Card>
@Query("UPDATE Card SET name = :newName WHERE id = :id") @Query("UPDATE Card SET name = :newName WHERE id = :id")
suspend fun renameCard(id: Int, newName: String) suspend fun renameCard(id: Int, newName: String)
@Query("DELETE FROM Card WHERE id = :id") @Query("DELETE FROM Card WHERE id = :id")
suspend fun deleteCard(id: Long) suspend fun deleteCard(id: Long)
@Query("""
SELECT
c.logo as cardIcon,
c.logoWidth as cardIconWidth,
c.logoHeight as cardIconHeight
FROM Card c
JOIN CardCharacter cc ON cc.cardId = c.id
WHERE cc.id = :charaId
""")
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon>
} }

View File

@ -0,0 +1,48 @@
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("""
INSERT INTO
CardFusions (
fromCharaId,
attribute,
toCharaId
)
SELECT
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :fromCharaId),
:attribute,
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaId)
""")
suspend fun insertNewFusion(
cardId: Long,
fromCharaId: Int,
attribute: NfcCharacter.Attribute,
toCharaId: 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>>
}

View File

@ -1,17 +1,29 @@
package com.github.nacabaro.vbhelper.daos package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.card.CardProgress import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface CardProgressDao { interface CardProgressDao {
@Upsert @Query("""
fun updateDimProgress(vararg cardProgresses: CardProgress) UPDATE CardProgress
SET
currentStage = :currentStage,
unlocked = :unlocked
WHERE cardId = :cardId AND
currentStage < :currentStage
""")
fun updateCardProgress(currentStage: Int, cardId: Long, unlocked: Boolean)
@Query( @Query(
"SELECT currentStage FROM CardProgress WHERE cardId = :cardId" "SELECT currentStage FROM CardProgress WHERE cardId = :cardId"
) )
fun getCardProgress(cardId: Int): Int fun getCardProgress(cardId: Long): Flow<Int>
@Insert
fun insertCardProgress(cardProgress: CardProgress)
} }

View File

@ -3,17 +3,18 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.characters.Character import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.characters.Sprite import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface CharacterDao { interface CharacterDao {
@Insert @Insert
suspend fun insertCharacter(vararg characterData: Character) suspend fun insertCharacter(vararg characterData: CardCharacter)
@Query("SELECT * FROM Character WHERE monIndex = :monIndex AND dimId = :dimId LIMIT 1") @Query("SELECT * FROM CardCharacter WHERE charaIndex = :monIndex AND cardId = :dimId LIMIT 1")
fun getCharacterByMonIndex(monIndex: Int, dimId: Long): Character fun getCharacterByMonIndex(monIndex: Int, dimId: Long): CardCharacter
@Insert @Insert
suspend fun insertSprite(vararg sprite: Sprite) suspend fun insertSprite(vararg sprite: Sprite)
@ -22,14 +23,68 @@ interface CharacterDao {
""" """
SELECT SELECT
d.cardId as cardId, d.cardId as cardId,
c.monIndex as charId, c.charaIndex as charId,
c.stage as stage, c.stage as stage,
c.attribute as attribute c.attribute as attribute,
FROM Character c cp.currentStage as currentStage
FROM CardCharacter c
JOIN UserCharacter uc ON c.id = uc.charId JOIN UserCharacter uc ON c.id = uc.charId
JOIN Card d ON c.dimId = d.id JOIN Card d ON c.cardId = d.id
JOIN CardProgress cp ON d.id = cp.cardId
WHERE c.id = :charId WHERE c.id = :charId
""" """
) )
suspend fun getCharacterInfo(charId: Long): CharacterDtos.CardCharacterInfo suspend fun getCharacterInfo(charId: Long): CharacterDtos.CardCharacterInfo
@Query(
"""
INSERT INTO PossibleTransformations (charaId, requiredVitals, requiredTrophies, requiredBattles, requiredWinRate, changeTimerHours, requiredAdventureLevelCompleted, toCharaId)
SELECT
(SELECT id FROM CardCharacter WHERE charaIndex = :fromChraraIndex AND cardId = :cardId),
:requiredVitals,
:requiredTrophies,
:requiredBattles,
:requiredWinRate,
:changeTimerHours,
:requiredAdventureLevelCompleted,
(SELECT id FROM CardCharacter WHERE charaIndex = :toChraraIndex AND cardId = :cardId)
"""
)
suspend fun insertPossibleTransformation(
fromChraraIndex: Int,
toChraraIndex: Int,
cardId: Long,
requiredVitals: Int,
requiredTrophies: Int,
requiredBattles: Int,
changeTimerHours: Int,
requiredWinRate: Int,
requiredAdventureLevelCompleted: Int
)
@Query(
"""
SELECT
pt.charaId as fromCharaId,
pt.toCharaId as charaId,
s.spriteIdle1 as spriteIdle,
s.width as spriteWidth,
s.height as spriteHeight,
d.discoveredOn as discoveredOn,
pt.requiredTrophies as requiredTrophies,
pt.requiredVitals as requiredVitals,
pt.requiredBattles as requiredBattles,
pt.requiredWinRate as requiredWinRate,
pt.changeTimerHours as changeTimerHours,
pt.requiredAdventureLevelCompleted as requiredAdventureLevelCompleted
FROM
PossibleTransformations 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
"""
)
fun getEvolutionRequirementsForCard(characterId: Long): Flow<List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>>
} }

View File

@ -4,42 +4,57 @@ import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CardDtos import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface DexDao { interface DexDao {
@Query(""" @Query(
"""
INSERT OR IGNORE INTO Dex(id, discoveredOn) INSERT OR IGNORE INTO Dex(id, discoveredOn)
VALUES ( VALUES (
(SELECT id FROM Character WHERE monIndex = :charIndex AND dimId = :cardId), (SELECT id FROM CardCharacter WHERE charaIndex = :charIndex AND cardId = :cardId),
:discoveredOn :discoveredOn
) )
""") """
)
fun insertCharacter(charIndex: Int, cardId: Long, discoveredOn: Long) fun insertCharacter(charIndex: Int, cardId: Long, discoveredOn: Long)
@Query(""" @Query(
"""
SELECT SELECT
c.id AS id, c.id AS id,
s.spriteIdle1 AS spriteIdle, s.spriteIdle1 AS spriteIdle,
s.width AS spriteWidth, s.width AS spriteWidth,
s.height AS spriteHeight, s.height AS spriteHeight,
d.discoveredOn AS discoveredOn c.nameSprite AS nameSprite,
FROM Character c c.nameWidth AS nameSpriteWidth,
c.nameHeight AS nameSpriteHeight,
d.discoveredOn AS discoveredOn,
c.baseHp as baseHp,
c.baseBp as baseBp,
c.baseAp as baseAp,
c.stage as stage,
c.attribute as attribute
FROM CardCharacter c
JOIN Sprite s ON c.spriteId = s.id JOIN Sprite s ON c.spriteId = s.id
LEFT JOIN dex d ON c.id = d.id LEFT JOIN dex d ON c.id = d.id
WHERE c.dimId = :cardId WHERE c.cardId = :cardId
""") """
suspend fun getSingleCardProgress(cardId: Long): List<CharacterDtos.CardProgress> )
fun getSingleCardProgress(cardId: Long): Flow<List<CharacterDtos.CardCharaProgress>>
@Query(""" @Query(
"""
SELECT SELECT
c.id as cardId, c.id as cardId,
c.name as cardName, c.name as cardName,
c.logo as cardLogo, c.logo as cardLogo,
c.logoWidth as logoWidth, c.logoWidth as logoWidth,
c.logoHeight as logoHeight, c.logoHeight as logoHeight,
(SELECT COUNT(*) FROM Character cc WHERE cc.dimId = c.id) AS totalCharacters, (SELECT COUNT(*) FROM CardCharacter cc WHERE cc.cardId = c.id) AS totalCharacters,
(SELECT COUNT(*) FROM Dex d JOIN Character cc ON d.id = cc.id WHERE cc.dimId = c.id AND d.discoveredOn IS NOT NULL) AS obtainedCharacters (SELECT COUNT(*) FROM Dex d JOIN CardCharacter cc ON d.id = cc.id WHERE cc.cardId = c.id AND d.discoveredOn IS NOT NULL) AS obtainedCharacters
FROM Card c FROM Card c
""") """
suspend fun getCardsWithProgress(): List<CardDtos.CardProgress> )
fun getCardsWithProgress(): Flow<List<CardDtos.CardProgress>>
} }

View File

@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface ItemDao { interface ItemDao {
@ -13,7 +14,7 @@ interface ItemDao {
ORDER BY Items.itemIcon ASC ORDER BY Items.itemIcon ASC
""" """
) )
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities> fun getAllItems(): Flow<List<ItemDtos.ItemsWithQuantities>>
@Query( @Query(
""" """
@ -22,7 +23,7 @@ interface ItemDao {
WHERE quantity > 0 WHERE quantity > 0
""" """
) )
suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities> fun getAllUserItems(): Flow<List<ItemDtos.ItemsWithQuantities>>
@Query( @Query(
""" """

View File

@ -2,6 +2,8 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface SpecialMissionDao { interface SpecialMissionDao {
@ -12,4 +14,11 @@ interface SpecialMissionDao {
WHERE id = :id WHERE id = :id
""") """)
suspend fun clearSpecialMission(id: Long) suspend fun clearSpecialMission(id: Long)
@Query("""
SELECT *
FROM SpecialMissions
WHERE id = :id
""")
fun getSpecialMission(id: Long): Flow<SpecialMissions>
} }

View File

@ -5,7 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Upsert import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.characters.Character import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
@ -13,6 +13,7 @@ import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface UserCharacterDao { interface UserCharacterDao {
@ -37,20 +38,22 @@ interface UserCharacterDao {
@Upsert @Upsert
fun insertSpecialMissions(vararg specialMissions: SpecialMissions) fun insertSpecialMissions(vararg specialMissions: SpecialMissions)
@Query(""" @Query(
"""
SELECT SELECT
c.id AS id, c.id AS id,
s.spriteIdle1 AS spriteIdle, s.spriteIdle1 AS spriteIdle,
s.width AS spriteWidth, s.width AS spriteWidth,
s.height AS spriteHeight, s.height AS spriteHeight,
c.monIndex AS monIndex, c.charaIndex AS monIndex,
t.transformationDate AS transformationDate t.transformationDate AS transformationDate
FROM TransformationHistory t FROM TransformationHistory t
JOIN Character c ON c.id = t.stageId JOIN CardCharacter c ON c.id = t.stageId
JOIN Sprite s ON s.id = c.spriteId JOIN Sprite s ON s.id = c.spriteId
WHERE monId = :monId WHERE monId = :monId
""") """
suspend fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>? )
fun getTransformationHistory(monId: Long): Flow<List<CharacterDtos.TransformationHistory>>
@Query( @Query(
""" """
@ -62,19 +65,20 @@ interface UserCharacterDao {
s.spriteIdle2 AS spriteIdle2, s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth, s.width AS spriteWidth,
s.height AS spriteHeight, s.height AS spriteHeight,
c.name as nameSprite, c.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.dimId JOIN Card d ON d.id = c.cardId
JOIN Sprite s ON s.id = c.spriteId JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id LEFT JOIN Adventure a ON a.characterId = uc.id
""" """
) )
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites> fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>>
@Query( @Query(
""" """
@ -86,31 +90,33 @@ interface UserCharacterDao {
s.spriteIdle2 AS spriteIdle2, s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth, s.width AS spriteWidth,
s.height AS spriteHeight, s.height AS spriteHeight,
c.name as nameSprite, c.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id JOIN Card d ON c.cardId = d.id
JOIN Sprite s ON s.id = c.spriteId JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.id = :id WHERE uc.id = :id
""") """
)
suspend fun getCharacterWithSprites(id: Long): CharacterDtos.CharacterWithSprites suspend fun getCharacterWithSprites(id: Long): CharacterDtos.CharacterWithSprites
@Query("SELECT * FROM UserCharacter WHERE id = :id") @Query("SELECT * FROM UserCharacter WHERE id = :id")
suspend fun getCharacter(id: Long): UserCharacter suspend fun getCharacter(id: Long): UserCharacter
@Query("SELECT * FROM BECharacterData WHERE id = :id") @Query("SELECT * FROM BECharacterData WHERE id = :id")
suspend fun getBeData(id: Long): BECharacterData fun getBeData(id: Long): Flow<BECharacterData>
@Query("SELECT * FROM VBCharacterData WHERE id = :id") @Query("SELECT * FROM VBCharacterData WHERE id = :id")
suspend fun getVbData(id: Long): VBCharacterData fun getVbData(id: Long): Flow<VBCharacterData>
@Query("SELECT * FROM SpecialMissions WHERE characterId = :id") @Query("SELECT * FROM SpecialMissions WHERE characterId = :id")
suspend fun getSpecialMissions(id: Long): List<SpecialMissions> fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>>
@Query( @Query(
""" """
@ -122,20 +128,22 @@ interface UserCharacterDao {
s.spriteIdle2 AS spriteIdle2, s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth, s.width AS spriteWidth,
s.height AS spriteHeight, s.height AS spriteHeight,
c.name as nameSprite, c.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId as isInAdventure a.characterId as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id JOIN Card d ON c.cardId = d.id
JOIN Sprite s ON s.id = c.spriteId JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.isActive = 1 WHERE uc.isActive = 1
LIMIT 1 LIMIT 1
""") """
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? )
fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?>
@Query("DELETE FROM UserCharacter WHERE id = :id") @Query("DELETE FROM UserCharacter WHERE id = :id")
fun deleteCharacterById(id: Long) fun deleteCharacterById(id: Long)
@ -149,22 +157,24 @@ interface UserCharacterDao {
@Query( @Query(
""" """
SELECT c.* SELECT c.*
FROM Character c FROM CardCharacter c
join UserCharacter uc on c.id = uc.charId join UserCharacter uc on c.id = uc.charId
where uc.id = :charId where uc.id = :charId
LIMIT 1 LIMIT 1
""" """
) )
suspend fun getCharacterInfo(charId: Long): Character suspend fun getCharacterInfo(charId: Long): CardCharacter
@Query(""" @Query(
"""
INSERT INTO TransformationHistory(monId, stageId, transformationDate) INSERT INTO TransformationHistory(monId, stageId, transformationDate)
VALUES VALUES
(:monId, (:monId,
(SELECT id FROM Character WHERE monIndex = :stage AND dimId = :dimId), (SELECT id FROM CardCharacter WHERE charaIndex = :stage AND cardId = :dimId),
:transformationDate) :transformationDate)
""") """
)
fun insertTransformation(monId: Long, stage: Int, dimId: Long, transformationDate: Long) fun insertTransformation(monId: Long, stage: Int, dimId: Long, transformationDate: Long)
@Upsert @Upsert
@ -183,14 +193,15 @@ interface UserCharacterDao {
s.spriteIdle2 AS spriteIdle2, s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth, s.width AS spriteWidth,
s.height AS spriteHeight, s.height AS spriteHeight,
c.name as nameSprite, c.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.dimId JOIN Card d ON d.id = c.cardId
JOIN Sprite s ON s.id = c.spriteId JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.characterType = "BEDevice" WHERE uc.characterType = "BEDevice"
@ -208,14 +219,15 @@ interface UserCharacterDao {
s.spriteIdle2 AS spriteIdle2, s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth, s.width AS spriteWidth,
s.height AS spriteHeight, s.height AS spriteHeight,
c.name as nameSprite, c.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.dimId JOIN Card d ON d.id = c.cardId
JOIN Sprite s ON s.id = c.spriteId JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.characterType = "VBDevice" WHERE uc.characterType = "VBDevice"

View File

@ -3,17 +3,23 @@ package com.github.nacabaro.vbhelper.database
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.daos.AdventureDao import com.github.nacabaro.vbhelper.daos.AdventureDao
import com.github.nacabaro.vbhelper.daos.CardAdventureDao
import com.github.nacabaro.vbhelper.daos.CharacterDao import com.github.nacabaro.vbhelper.daos.CharacterDao
import com.github.nacabaro.vbhelper.daos.DexDao import com.github.nacabaro.vbhelper.daos.DexDao
import com.github.nacabaro.vbhelper.daos.CardDao import com.github.nacabaro.vbhelper.daos.CardDao
import com.github.nacabaro.vbhelper.daos.CardFusionsDao
import com.github.nacabaro.vbhelper.daos.CardProgressDao import com.github.nacabaro.vbhelper.daos.CardProgressDao
import com.github.nacabaro.vbhelper.daos.ItemDao import com.github.nacabaro.vbhelper.daos.ItemDao
import com.github.nacabaro.vbhelper.daos.SpecialMissionDao import com.github.nacabaro.vbhelper.daos.SpecialMissionDao
import com.github.nacabaro.vbhelper.daos.SpriteDao import com.github.nacabaro.vbhelper.daos.SpriteDao
import com.github.nacabaro.vbhelper.daos.UserCharacterDao import com.github.nacabaro.vbhelper.daos.UserCharacterDao
import com.github.nacabaro.vbhelper.domain.characters.Character import com.github.nacabaro.vbhelper.domain.card.Background
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardAdventure
import com.github.nacabaro.vbhelper.domain.card.CardFusions
import com.github.nacabaro.vbhelper.domain.card.CardProgress import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.card.PossibleTransformations
import com.github.nacabaro.vbhelper.domain.characters.Sprite import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.domain.characters.Adventure import com.github.nacabaro.vbhelper.domain.characters.Adventure
import com.github.nacabaro.vbhelper.domain.characters.Dex import com.github.nacabaro.vbhelper.domain.characters.Dex
@ -30,7 +36,9 @@ import com.github.nacabaro.vbhelper.domain.items.Items
entities = [ entities = [
Card::class, Card::class,
CardProgress::class, CardProgress::class,
Character::class, CardCharacter::class,
CardAdventure::class,
CardFusions::class,
Sprite::class, Sprite::class,
UserCharacter::class, UserCharacter::class,
BECharacterData::class, BECharacterData::class,
@ -40,7 +48,9 @@ import com.github.nacabaro.vbhelper.domain.items.Items
VitalsHistory::class, VitalsHistory::class,
Dex::class, Dex::class,
Items::class, Items::class,
Adventure::class Adventure::class,
Background::class,
PossibleTransformations::class
] ]
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
@ -53,4 +63,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun adventureDao(): AdventureDao abstract fun adventureDao(): AdventureDao
abstract fun spriteDao(): SpriteDao abstract fun spriteDao(): SpriteDao
abstract fun specialMissionDao(): SpecialMissionDao abstract fun specialMissionDao(): SpecialMissionDao
abstract fun cardAdventureDao(): CardAdventureDao
abstract fun cardFusionsDao(): CardFusionsDao
} }

View File

@ -1,9 +1,11 @@
package com.github.nacabaro.vbhelper.di package com.github.nacabaro.vbhelper.di
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
interface AppContainer { interface AppContainer {
val db: AppDatabase val db: AppDatabase
val dataStoreSecretsRepository: DataStoreSecretsRepository val dataStoreSecretsRepository: DataStoreSecretsRepository
val currencyRepository: CurrencyRepository
} }

View File

@ -1,20 +1,27 @@
import android.content.Context import android.content.Context
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.dataStore import androidx.datastore.dataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import androidx.room.Room import androidx.room.Room
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.AppContainer import com.github.nacabaro.vbhelper.di.AppContainer
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
import com.github.nacabaro.vbhelper.source.SecretsSerializer import com.github.nacabaro.vbhelper.source.SecretsSerializer
import com.github.nacabaro.vbhelper.source.proto.Secrets import com.github.nacabaro.vbhelper.source.proto.Secrets
private const val SECRETS_DATA_STORE_NAME = "secrets.pb" private const val SECRETS_DATA_STORE_NAME = "secrets.pb"
private const val USER_PREFERENCES_NAME = "user_preferences"
val Context.secretsStore: DataStore<Secrets> by dataStore( val Context.secretsStore: DataStore<Secrets> by dataStore(
fileName = SECRETS_DATA_STORE_NAME, fileName = SECRETS_DATA_STORE_NAME,
serializer = SecretsSerializer serializer = SecretsSerializer
) )
val Context.currencyStore: DataStore<Preferences> by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
class DefaultAppContainer(private val context: Context) : AppContainer { class DefaultAppContainer(private val context: Context) : AppContainer {
override val db: AppDatabase by lazy { override val db: AppDatabase by lazy {
@ -29,5 +36,6 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore) override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
override val currencyRepository = CurrencyRepository(context.currencyStore)
} }

View File

@ -3,7 +3,6 @@ package com.github.nacabaro.vbhelper.domain
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.characters.Character
@Entity( @Entity(
foreignKeys = [ foreignKeys = [

View File

@ -3,7 +3,6 @@ package com.github.nacabaro.vbhelper.domain
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.characters.Character
@Entity( @Entity(
foreignKeys = [ foreignKeys = [

View File

@ -0,0 +1,23 @@
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = Card::class,
parentColumns = ["id"],
childColumns = ["cardId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Background (
@PrimaryKey(autoGenerate = true) val id: Long,
val cardId: Long,
val background: ByteArray,
val backgroundWidth: Int,
val backgroundHeight: Int
)

View File

@ -0,0 +1,32 @@
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["characterId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Card::class,
parentColumns = ["id"],
childColumns = ["cardId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class CardAdventure(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val cardId: Long,
val characterId: Long,
val steps: Int,
val bossHp: Int,
val bossAp: Int,
val bossDp: Int,
val bossBp: Int?
)

View File

@ -1,17 +1,17 @@
package com.github.nacabaro.vbhelper.domain.characters package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.characters.Sprite
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
ForeignKey( ForeignKey(
entity = Card::class, entity = Card::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["dimId"], childColumns = ["cardId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
), ),
ForeignKey( ForeignKey(
@ -28,17 +28,17 @@ import com.github.nacabaro.vbhelper.domain.card.Card
* and monIndex. * and monIndex.
* TODO: Customs will mean this should be unique per cardName and monIndex * TODO: Customs will mean this should be unique per cardName and monIndex
*/ */
data class Character ( data class CardCharacter (
@PrimaryKey(autoGenerate = true) val id: Long = 0, @PrimaryKey(autoGenerate = true) val id: Long = 0,
val dimId: Long, val cardId: Long,
val spriteId: Long, val spriteId: Long,
val monIndex: Int, val charaIndex: Int,
val name: ByteArray,
val stage: Int, // These should be replaced with enums val stage: Int, // These should be replaced with enums
val attribute: NfcCharacter.Attribute, // This one too val attribute: NfcCharacter.Attribute, // This one too
val baseHp: Int, val baseHp: Int,
val baseBp: Int, val baseBp: Int,
val baseAp: Int, val baseAp: Int,
val nameSprite: ByteArray,
val nameWidth: Int, val nameWidth: Int,
val nameHeight: Int val nameHeight: Int
) )

View File

@ -0,0 +1,29 @@
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 = [
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["fromCharaId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["toCharaId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class CardFusions(
@PrimaryKey(autoGenerate = true) val id: Long,
val fromCharaId: Long,
val attribute: NfcCharacter.Attribute,
val toCharaId: Long
)

View File

@ -0,0 +1,33 @@
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["charaId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["toCharaId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class PossibleTransformations (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
var charaId: Long,
val requiredVitals: Int,
val requiredTrophies: Int,
val requiredBattles: Int,
val requiredWinRate: Int,
val changeTimerHours: Int,
val requiredAdventureLevelCompleted: Int,
val toCharaId: Long?
)

View File

@ -3,11 +3,12 @@ package com.github.nacabaro.vbhelper.domain.characters
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
ForeignKey( ForeignKey(
entity = Character::class, entity = CardCharacter::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["id"], childColumns = ["id"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE

View File

@ -3,7 +3,7 @@ package com.github.nacabaro.vbhelper.domain.device_data
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.characters.Character import com.github.nacabaro.vbhelper.domain.card.CardCharacter
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
@ -14,7 +14,7 @@ import com.github.nacabaro.vbhelper.domain.characters.Character
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
), ),
ForeignKey( ForeignKey(
entity = Character::class, entity = CardCharacter::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["stageId"], childColumns = ["stageId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE

View File

@ -5,12 +5,12 @@ import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import com.github.nacabaro.vbhelper.domain.characters.Character import com.github.nacabaro.vbhelper.domain.card.CardCharacter
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
ForeignKey( ForeignKey(
entity = Character::class, entity = CardCharacter::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["charId"], childColumns = ["charId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE

View File

@ -10,4 +10,24 @@ object CardDtos {
val totalCharacters: Int, val totalCharacters: Int,
val obtainedCharacters: Int, val obtainedCharacters: Int,
) )
data class CardAdventureWithSprites (
val characterName: ByteArray,
val characterNameWidth: Int,
val characterNameHeight: Int,
val characterIdleSprite: ByteArray,
val characterIdleSpriteWidth: Int,
val characterIdleSpriteHeight: Int,
val characterAp: Int,
val characterBp: Int?,
val characterDp: Int,
val characterHp: Int,
val steps: Int,
)
data class CardIcon (
val cardIcon: ByteArray,
val cardIconWidth: Int,
val cardIconHeight: Int
)
} }

View File

@ -31,14 +31,16 @@ object CharacterDtos {
val nameSpriteWidth: Int, val nameSpriteWidth: Int,
val nameSpriteHeight: Int, val nameSpriteHeight: Int,
val isBemCard: Boolean, val isBemCard: Boolean,
val isInAdventure: Boolean val isInAdventure: Boolean,
val active: Boolean
) )
data class CardCharacterInfo( data class CardCharacterInfo(
val cardId: Int, val cardId: Long,
val charId: Int, val charId: Int,
val stage: Int, val stage: Int,
val attribute: NfcCharacter.Attribute val attribute: NfcCharacter.Attribute,
val currentStage: Int
) )
data class TransformationHistory( data class TransformationHistory(
@ -50,12 +52,27 @@ object CharacterDtos {
val transformationDate: Long val transformationDate: Long
) )
data class CardCharaProgress(
val id: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val nameSprite: ByteArray,
val nameSpriteWidth: Int,
val nameSpriteHeight: Int,
val discoveredOn: Long?,
val baseHp: Int,
val baseBp: Int,
val baseAp: Int,
val stage: Int,
val attribute: NfcCharacter.Attribute,
)
data class CardProgress( data class CardProgress(
val id: Long, val id: Long,
val spriteIdle: ByteArray, val spriteIdle: ByteArray,
val spriteWidth: Int, val spriteWidth: Int,
val spriteHeight: Int, val spriteHeight: Int,
val discoveredOn: Long?
) )
data class AdventureCharacterWithSprites( data class AdventureCharacterWithSprites(
@ -83,4 +100,29 @@ object CharacterDtos {
val finishesAdventure: Long, val finishesAdventure: Long,
val originalTimeInMinutes: Long val originalTimeInMinutes: Long
) )
data class EvolutionRequirementsWithSpritesAndObtained(
val charaId: Long,
val fromCharaId: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val discoveredOn: Long?,
val requiredTrophies: Int,
val requiredVitals: Int,
val requiredBattles: Int,
val requiredWinRate: Int,
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
)
} }

View File

@ -1,13 +1,12 @@
package com.github.nacabaro.vbhelper.navigation package com.github.nacabaro.vbhelper.navigation
import android.util.Log
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -34,6 +33,7 @@ import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImp
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreen import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreen
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.cardScreen.CardAdventureScreen
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
@ -98,20 +98,14 @@ fun AppNavigation(
composable(NavigationItems.Scan.route) { composable(NavigationItems.Scan.route) {
val characterIdString = it.arguments?.getString("characterId") val characterIdString = it.arguments?.getString("characterId")
var characterId by remember { mutableStateOf(characterIdString?.toLongOrNull()) } var characterId by remember { mutableStateOf(characterIdString?.toLongOrNull()) }
Log.d("ScanScreen", "characterId: $characterId")
val launchedFromHomeScreen = (characterIdString?.toLongOrNull() == null) val launchedFromHomeScreen = (characterIdString?.toLongOrNull() == null)
if (characterId == null) { if (characterId == null) {
val context = LocalContext.current.applicationContext as VBHelper val context = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(context.container.db) val storageRepository = StorageRepository(context.container.db)
val characterData by storageRepository.getActiveCharacter().collectAsState(null)
LaunchedEffect(characterId) { if (characterData != null) {
if (characterId == null) { characterId = characterData!!.id
val characterData = storageRepository.getActiveCharacter()
if (characterData != null) {
characterId = characterData.id
}
}
} }
} }
@ -145,7 +139,7 @@ fun AppNavigation(
if (cardId != null) { if (cardId != null) {
CardViewScreen( CardViewScreen(
navController = navController, navController = navController,
dimId = cardId.toLong() cardId = cardId.toLong()
) )
} }
} }
@ -177,6 +171,17 @@ fun AppNavigation(
navController = navController navController = navController
) )
} }
composable(NavigationItems.CardAdventure.route) {
val cardId = it.arguments?.getString("cardId")
if (cardId != null) {
CardAdventureScreen(
navController = navController,
cardId = cardId.toLong(),
cardScreenController = applicationNavigationHandlers
.cardScreenController
)
}
}
} }
} }
} }

View File

@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.compose.ui.res.stringResource
@Composable @Composable
fun BottomNavigationBar(navController: NavController) { fun BottomNavigationBar(navController: NavController) {
@ -26,8 +26,8 @@ fun BottomNavigationBar(navController: NavController) {
items.forEach { item -> items.forEach { item ->
NavigationBarItem ( NavigationBarItem (
icon = { Icon(painter = painterResource(item.icon), contentDescription = item.label) }, icon = { Icon(painter = painterResource(item.icon), contentDescription = stringResource(item.label)) },
label = { Text(item.label) }, label = { Text(text = stringResource(item.label)) },
selected = currentRoute == item.route, selected = currentRoute == item.route,
onClick = { onClick = {
navController.navigate(item.route) { navController.navigate(item.route) {

View File

@ -1,24 +1,101 @@
package com.github.nacabaro.vbhelper.navigation package com.github.nacabaro.vbhelper.navigation
import com.github.nacabaro.vbhelper.R import com.github.nacabaro.vbhelper.R
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
sealed class NavigationItems ( sealed class NavigationItems(
var route: String, val route: String,
var icon: Int, @DrawableRes val icon: Int,
var label: String @StringRes val label: Int
) { ) {
object Scan : NavigationItems("Scan/{characterId}", R.drawable.baseline_nfc_24, "Scan") object Scan : NavigationItems(
object Battles : NavigationItems("Battle", R.drawable.baseline_swords_24, "Battle") "Scan/{characterId}",
object Home : NavigationItems("Home", R.drawable.baseline_cottage_24, "Home") R.drawable.baseline_nfc_24,
object Dex : NavigationItems("Dex", R.drawable.baseline_menu_book_24, "Dex") R.string.nav_scan
object Storage : NavigationItems("Storage", R.drawable.baseline_catching_pokemon_24, "Storage") )
object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings")
object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer") object Battles : NavigationItems(
object CardView : NavigationItems("Card/{cardId}", R.drawable.baseline_image_24, "Card") "Battle",
object Items : NavigationItems("Items", R.drawable.baseline_data_24, "Items") R.drawable.baseline_swords_24,
object MyItems : NavigationItems("MyItems", R.drawable.baseline_data_24, "My items") R.string.nav_battle
object ItemsStore : NavigationItems("ItemsStore", R.drawable.baseline_data_24, "Items store") )
object ApplyItem : NavigationItems("ApplyItem/{itemId}", R.drawable.baseline_data_24, "Apply item")
object Adventure : NavigationItems("Adventure", R.drawable.baseline_fort_24, "Adventure") object Home : NavigationItems(
object Credits : NavigationItems("Credits", R.drawable.baseline_data_24, "Credits") "Home",
R.drawable.baseline_cottage_24,
R.string.nav_home
)
object Dex : NavigationItems(
"Dex",
R.drawable.baseline_menu_book_24,
R.string.nav_dex
)
object CardAdventure : NavigationItems(
"CardAdventure/{cardId}",
R.drawable.baseline_fort_24,
R.string.nav_card_adventure
)
object Storage : NavigationItems(
"Storage",
R.drawable.baseline_catching_pokemon_24,
R.string.nav_storage
)
object Settings : NavigationItems(
"Settings",
R.drawable.baseline_settings_24,
R.string.nav_settings
)
object Viewer : NavigationItems(
"Viewer",
R.drawable.baseline_image_24,
R.string.nav_viewer
)
object CardView : NavigationItems(
"Card/{cardId}",
R.drawable.baseline_image_24,
R.string.nav_card
)
object Items : NavigationItems(
"Items",
R.drawable.baseline_data_24,
R.string.nav_items
)
object MyItems : NavigationItems(
"MyItems",
R.drawable.baseline_data_24,
R.string.nav_my_items
)
object ItemsStore : NavigationItems(
"ItemsStore",
R.drawable.baseline_data_24,
R.string.nav_items_store
)
object ApplyItem : NavigationItems(
"ApplyItem/{itemId}",
R.drawable.baseline_data_24,
R.string.nav_apply_item
)
object Adventure : NavigationItems(
"Adventure",
R.drawable.baseline_fort_24,
R.string.nav_adventure
)
object Credits : NavigationItems(
"Credits",
R.drawable.baseline_data_24,
R.string.nav_credits
)
} }

View File

@ -10,13 +10,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun BattlesScreen() { fun BattlesScreen() {
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "Online battles" text = stringResource(R.string.battles_online_title)
) )
} }
) { contentPadding -> ) { contentPadding ->
@ -27,7 +30,7 @@ fun BattlesScreen() {
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
.fillMaxSize() .fillMaxSize()
) { ) {
Text("Coming soon") Text(stringResource(R.string.battles_coming_soon))
} }
} }
} }

View File

@ -9,16 +9,16 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
@ -29,24 +29,25 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.Instant import java.time.Instant
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun AdventureScreen( fun AdventureScreen(
navController: NavController, navController: NavController,
storageScreenController: AdventureScreenControllerImpl storageScreenController: AdventureScreenControllerImpl
) { ) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db val database = application.container.db
val storageRepository = StorageRepository(database) val storageRepository = StorageRepository(database)
val characterList = remember { val characterList by storageRepository.getAdventureCharacters().collectAsState(emptyList())
mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
}
var obtainedItem by remember { var obtainedItem by remember {
mutableStateOf<ItemDtos.PurchasedItem?>(null) mutableStateOf<ItemDtos.PurchasedItem?>(null)
} }
var obtainedCurrency by remember {
mutableStateOf(0)
}
val currentTime by produceState(initialValue = Instant.now().epochSecond) { val currentTime by produceState(initialValue = Instant.now().epochSecond) {
while (true) { while (true) {
@ -59,24 +60,17 @@ fun AdventureScreen(
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null) mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
} }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
characterList.value = storageRepository
.getAdventureCharacters()
}
}
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Adventure", text = stringResource(R.string.adventure_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
) )
} }
) { contentPadding -> ) { contentPadding ->
if (characterList.value.isEmpty()) { if (characterList.isEmpty()) {
Column( Column(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@ -84,14 +78,14 @@ fun AdventureScreen(
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
.fillMaxSize() .fillMaxSize()
) { ) {
Text(text = "Nothing to see here") Text(text = stringResource(R.string.adventure_empty_state))
} }
} else { } else {
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
items(characterList.value) { items(characterList) {
AdventureEntry( AdventureEntry(
icon = BitmapData( icon = BitmapData(
bitmap = it.spriteIdle, bitmap = it.spriteIdle,
@ -102,8 +96,9 @@ fun AdventureScreen(
onClick = { onClick = {
if (it.finishesAdventure < currentTime) { if (it.finishesAdventure < currentTime) {
storageScreenController storageScreenController
.getItemFromAdventure(it.id) { adventureResult -> .getItemFromAdventure(it.id) { adventureResult, generatedCurrency ->
obtainedItem = adventureResult obtainedItem = adventureResult
obtainedCurrency = generatedCurrency
} }
} else { } else {
cancelAdventureDialog = it cancelAdventureDialog = it
@ -118,6 +113,7 @@ fun AdventureScreen(
if (obtainedItem != null) { if (obtainedItem != null) {
ObtainedItemDialog( ObtainedItemDialog(
obtainedItem = obtainedItem!!, obtainedItem = obtainedItem!!,
obtainedCurrency = obtainedCurrency,
onClickDismiss = { onClickDismiss = {
obtainedItem = null obtainedItem = null
} }

View File

@ -4,6 +4,6 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface AdventureScreenController { interface AdventureScreenController {
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long) fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit) fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem, Int) -> Unit)
fun cancelAdventure(characterId: Long, onResult: () -> Unit) fun cancelAdventure(characterId: Long, onResult: () -> Unit)
} }

View File

@ -6,6 +6,7 @@ import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.random.Random import kotlin.random.Random
@ -37,19 +38,29 @@ class AdventureScreenControllerImpl(
override fun getItemFromAdventure( override fun getItemFromAdventure(
characterId: Long, characterId: Long,
onResult: (ItemDtos.PurchasedItem) -> Unit onResult: (ItemDtos.PurchasedItem, Int) -> Unit
) { ) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) { componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database database
.adventureDao() .adventureDao()
.deleteAdventure(characterId) .deleteAdventure(characterId)
val generatedCurrency = generateRandomCurrency()
val generatedItem = generateItem(characterId) val generatedItem = generateItem(characterId)
onResult(generatedItem) onResult(generatedItem, generatedCurrency)
} }
} }
private suspend fun generateRandomCurrency(): Int {
val currentValue = application.container.currencyRepository.currencyValue.first()
val random = (2..6).random() * 1000
application.container.currencyRepository.setCurrencyValue(currentValue + random)
return random
}
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) { override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) { componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database database
@ -77,6 +88,7 @@ class AdventureScreenControllerImpl(
val randomItem = database val randomItem = database
.itemDao() .itemDao()
.getAllItems() .getAllItems()
.first()
.random() .random()
val random = ((Random.nextFloat() * character.stage) + 3).roundToInt() val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()

View File

@ -0,0 +1,122 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
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.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 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 com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
@Composable
fun CardAdventureEntry(
cardAdventureEntry: CardDtos.CardAdventureWithSprites,
obscure: Boolean
) {
val charaImageBitmapData = BitmapData(
bitmap = cardAdventureEntry.characterIdleSprite,
width = cardAdventureEntry.characterIdleSpriteWidth,
height = cardAdventureEntry.characterIdleSpriteHeight
).getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = obscure
)
val nameImageBitmapData = BitmapData(
bitmap = cardAdventureEntry.characterName,
width = cardAdventureEntry.characterNameWidth,
height = cardAdventureEntry.characterNameHeight
).getImageBitmap(
context = LocalContext.current,
multiplier = 3,
obscure = obscure
)
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier
.padding(8.dp)
){
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(8.dp))
Column {
if (!obscure) {
Image(
bitmap = nameImageBitmapData.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.width(nameImageBitmapData.dpWidth)
.height(nameImageBitmapData.dpHeight),
filterQuality = FilterQuality.None
)
Spacer(modifier = Modifier.padding(4.dp))
Text(
text = "HP: ${cardAdventureEntry.characterHp}, DP: ${cardAdventureEntry.characterDp}, AP: ${cardAdventureEntry.characterAp}"
)
if (cardAdventureEntry.characterBp != null) {
Text(text = "BP: ${cardAdventureEntry.characterBp}")
}
Text(text = "Steps: ${cardAdventureEntry.steps}")
} else {
Text(text = "????????????????")
Text(
text = "HP: -, BP: -, AP: -"
)
if (cardAdventureEntry.characterBp != null) {
Text(text = "DP: -")
}
Text(text = "Steps: -")
}
}
}
}
}

View File

@ -0,0 +1,54 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
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.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun CardAdventureScreen(
navController: NavController,
cardScreenController: CardScreenControllerImpl,
cardId: Long
) {
val cardAdventureMissions by cardScreenController
.getCardAdventureMissions(cardId)
.collectAsState(emptyList())
val currentCardAdventure by cardScreenController
.getCardProgress(cardId)
.collectAsState(0)
Scaffold (
topBar = {
TopBanner(
text = stringResource(R.string.card_adventure_missions_title),
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.verticalScroll(state = rememberScrollState())
) {
cardAdventureMissions.mapIndexed { index, it ->
CardAdventureEntry(
cardAdventureEntry = it,
obscure = index > currentCardAdventure - 1
)
}
}
}
}

View File

@ -24,6 +24,9 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap import com.github.nacabaro.vbhelper.utils.getBitmap
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CardEntry( fun CardEntry(
@ -75,7 +78,11 @@ fun CardEntry(
modifier = Modifier modifier = Modifier
) )
Text( Text(
text = "$obtainedCharacters of $totalCharacters characters obtained", text = stringResource(
R.string.card_entry_characters_obtained,
obtainedCharacters,
totalCharacters
),
fontFamily = MaterialTheme.typography.labelSmall.fontFamily, fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
fontSize = MaterialTheme.typography.labelSmall.fontSize, fontSize = MaterialTheme.typography.labelSmall.fontSize,
modifier = Modifier modifier = Modifier
@ -91,7 +98,7 @@ fun CardEntry(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Edit, imageVector = Icons.Default.Edit,
contentDescription = "Edit" contentDescription = stringResource(R.string.card_entry_edit)
) )
} }
IconButton( IconButton(
@ -99,7 +106,7 @@ fun CardEntry(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Delete, imageVector = Icons.Default.Delete,
contentDescription = "Delete" contentDescription = stringResource(R.string.card_entry_delete)
) )
} }
} }

View File

@ -1,6 +1,13 @@
package com.github.nacabaro.vbhelper.screens.cardScreen package com.github.nacabaro.vbhelper.screens.cardScreen
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
interface CardScreenController { interface CardScreenController {
fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit) fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit)
fun deleteCard(cardId: Long, onDeleted: () -> Unit) fun deleteCard(cardId: Long, onDeleted: () -> Unit)
fun getCardAdventureMissions(cardId: Long): Flow<List<CardDtos.CardAdventureWithSprites>>
fun getCardProgress(cardId: Long): Flow<Int>
fun getFusionsForCharacters(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>>
} }

View File

@ -3,6 +3,9 @@ package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class CardScreenControllerImpl( class CardScreenControllerImpl(
@ -11,7 +14,6 @@ class CardScreenControllerImpl(
private val application = componentActivity.applicationContext as VBHelper private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db private val database = application.container.db
override fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit) { override fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit) {
componentActivity.lifecycleScope.launch { componentActivity.lifecycleScope.launch {
database database
@ -31,4 +33,23 @@ class CardScreenControllerImpl(
onDeleted() onDeleted()
} }
} }
override fun getCardAdventureMissions(cardId: Long): Flow<List<CardDtos.CardAdventureWithSprites>> {
return database
.cardAdventureDao()
.getAdventureForCard(cardId)
}
override fun getCardProgress(cardId: Long): Flow<Int> {
return database
.cardProgressDao()
.getCardProgress(cardId)
}
override fun getFusionsForCharacters(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>> {
return database
.cardFusionsDao()
.getFusionsForCharacter(characterId)
}
} }

View File

@ -5,44 +5,53 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.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 com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CardViewScreen( fun CardViewScreen(
navController: NavController, navController: NavController,
dimId: Long cardId: Long
) { ) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db) val dexRepository = DexRepository(application.container.db)
val characterList = remember { mutableStateOf<List<CharacterDtos.CardProgress>>(emptyList()) } val characterList by dexRepository.getCharactersByCardId(cardId).collectAsState(emptyList())
LaunchedEffect(dexRepository) { val selectedCharacter = remember { mutableStateOf<CharacterDtos.CardCharaProgress?>(null) }
coroutineScope.launch {
val newCharacterList = dexRepository.getCharactersByDimId(dimId)
characterList.value = newCharacterList
}
}
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "Discovered characters", text = stringResource(R.string.card_view_discovered_characters),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
},
onAdventureClick = {
navController
.navigate(route = NavigationItems
.CardAdventure
.route
.replace(
"{cardId}",
cardId.toString()
)
)
} }
) )
} }
@ -51,9 +60,11 @@ fun CardViewScreen(
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
contentPadding = contentPadding contentPadding = contentPadding
) { ) {
items(characterList.value) { character -> items(characterList) { character ->
CharacterEntry( CharacterEntry(
onClick = { }, onClick = {
selectedCharacter.value = character
},
obscure = character.discoveredOn == null, obscure = character.discoveredOn == null,
icon = BitmapData( icon = BitmapData(
bitmap = character.spriteIdle, bitmap = character.spriteIdle,
@ -63,5 +74,15 @@ fun CardViewScreen(
) )
} }
} }
if (selectedCharacter.value != null) {
DexCharaDetailsDialog(
currentChara = selectedCharacter.value!!,
obscure = selectedCharacter.value!!.discoveredOn == null,
onClickClose = {
selectedCharacter.value = null
}
)
}
} }
} }

View File

@ -7,11 +7,10 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@ -25,17 +24,17 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardDeleteDialog import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardDeleteDialog
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardRenameDialog import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardRenameDialog
import com.github.nacabaro.vbhelper.source.DexRepository import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CardsScreen( fun CardsScreen(
navController: NavController, navController: NavController,
cardScreenController: CardScreenControllerImpl cardScreenController: CardScreenControllerImpl
) { ) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db) val dexRepository = DexRepository(application.container.db)
val cardList = remember { mutableStateOf<List<CardDtos.CardProgress>>(emptyList()) } val cardList by dexRepository.getAllDims().collectAsState(emptyList())
val selectedCard = remember { mutableStateOf<CardDtos.CardProgress?>(null) } val selectedCard = remember { mutableStateOf<CardDtos.CardProgress?>(null) }
var clickedDelete by remember { mutableStateOf(false) } var clickedDelete by remember { mutableStateOf(false) }
@ -43,17 +42,10 @@ fun CardsScreen(
var modifyCards by remember { mutableStateOf(false) } var modifyCards by remember { mutableStateOf(false) }
LaunchedEffect(dexRepository) {
coroutineScope.launch {
val newDimList = dexRepository.getAllDims()
cardList.value = newDimList
}
}
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "My cards", text = stringResource(R.string.cards_my_cards_title),
onModifyClick = { onModifyClick = {
modifyCards = !modifyCards modifyCards = !modifyCards
} }
@ -64,7 +56,7 @@ fun CardsScreen(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
items(cardList.value) { items(cardList) {
CardEntry( CardEntry(
name = it.cardName, name = it.cardName,
logo = BitmapData( logo = BitmapData(

View File

@ -0,0 +1,287 @@
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 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
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
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun DexCharaDetailsDialog(
currentChara: CharacterDtos.CardCharaProgress,
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 romanNumeralsStage = when (currentChara.stage) {
1 -> "II"
2 -> "III"
3 -> "IV"
4 -> "V"
5 -> "VI"
6 -> "VII"
else -> "I"
}
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 = onClickClose
) {
Card (
modifier = Modifier
.fillMaxWidth()
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
Row (
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
) {
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(R.string.dex_chara_icon_description),
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 = stringResource(R.string.dex_chara_name_icon_description),
modifier = Modifier
.width(nameImageBitmapData.dpWidth)
.height(nameImageBitmapData.dpHeight),
filterQuality = FilterQuality.None
)
Spacer(modifier = Modifier.padding(4.dp))
if (currentChara.baseHp != 65535) {
Text(
text = stringResource(
R.string.dex_chara_stats,
currentChara.baseHp,
currentChara.baseBp,
currentChara.baseAp
)
)
Text(
text = stringResource(
R.string.dex_chara_stage_attribute,
romanNumeralsStage,
currentChara.attribute.toString().substring(0, 2)
)
)
}
}
} else {
Column {
Text(stringResource(R.string.dex_chara_unknown_name))
Spacer(modifier = Modifier.padding(4.dp))
Text(stringResource(R.string.dex_chara_stage_attribute_unknown))
Text(stringResource(R.string.dex_chara_stats_unknown))
}
}
}
Spacer(modifier = Modifier.padding(16.dp))
Column {
currentCharaPossibleTransformations.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 = stringResource(R.string.dex_chara_icon_description),
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(
text = stringResource(
R.string.dex_chara_requirements,
it.requiredTrophies,
it.requiredBattles,
it.requiredVitals,
it.requiredWinRate,
it.changeTimerHours
)
)
Text(
text = stringResource(
R.string.dex_chara_adventure_level,
it.requiredAdventureLevelCompleted + 1
)
)
}
}
}
}
}
Row {
if (currentCharaPossibleFusions.isNotEmpty()) {
Button(
onClick = {
showFusions = true
}
) {
Text(stringResource(R.string.dex_chara_fusions_button))
}
}
Spacer(
modifier = Modifier
.padding(4.dp)
)
Button(
onClick = onClickClose
) {
Text(stringResource(R.string.dex_chara_close_button))
}
}
}
}
}
if (showFusions) {
DexCharaFusionsDialog(
currentChara = currentChara,
currentCharaPossibleFusions = currentCharaPossibleFusions,
onClickDismiss = {
showFusions = false
},
obscure = obscure
)
}
}

View File

@ -0,0 +1,191 @@
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")
}
}
}
}
}

View File

@ -8,8 +8,10 @@ import androidx.compose.material3.Card
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun BetaWarning( fun BetaWarning(
@ -19,26 +21,26 @@ fun BetaWarning(
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Card { Card {
Column ( Column(
modifier = Modifier modifier = Modifier
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = "This application is currently in alpha and it is not complete. Do not use to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!" text = stringResource(R.string.beta_warning_message_main)
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Text( Text(
text = "Application should work now with the original VB and the VH." text = stringResource(R.string.beta_warning_message_compatibility)
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Text( Text(
text = "Thank you for your understanding and patience. Sincerely, the dev team." text = stringResource(R.string.beta_warning_message_thanks)
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Button( Button(
onClick = onDismissRequest onClick = onDismissRequest
) { ) {
Text(text = "Dismiss") Text(text = stringResource(R.string.beta_warning_button_dismiss))
} }
} }
} }

View File

@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.screens.homeScreens package com.github.nacabaro.vbhelper.screens.homeScreens
import android.util.Log
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -12,6 +11,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -20,6 +20,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
@ -28,9 +29,7 @@ import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEBEmHomeScreen import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEBEmHomeScreen
@ -38,8 +37,12 @@ import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.VBDiMHomeScreen import com.github.nacabaro.vbhelper.screens.homeScreens.screens.VBDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers import com.github.nacabaro.vbhelper.R
import kotlinx.coroutines.withContext import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.source.CardRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.flowOf
import kotlin.collections.emptyList
@Composable @Composable
fun HomeScreen( fun HomeScreen(
@ -48,28 +51,59 @@ fun HomeScreen(
) { ) {
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db) val storageRepository = StorageRepository(application.container.db)
val activeMon = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) } val cardRepository = CardRepository(application.container.db)
val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
val beData = remember { mutableStateOf<BECharacterData?>(null) } val activeMon by storageRepository
val vbData = remember { mutableStateOf<VBCharacterData?>(null) } .getActiveCharacter()
val vbSpecialMissions = remember { mutableStateOf<List<SpecialMissions>>(emptyList()) } .collectAsState(initial = null)
val cardIconData by (
activeMon
?.let { chara ->
cardRepository.getCardIconByCharaId(chara.charId)
}
?: flowOf<CardDtos.CardIcon?>(null)
).collectAsState(initial = null)
val transformationHistory by (
activeMon
?.let { chara ->
storageRepository.getTransformationHistory(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbSpecialMissions by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getSpecialMissions(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbData by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getCharacterVbData(chara.id)
}
?: flowOf<VBCharacterData?>(null)
).collectAsState(initial = null)
val beData by (
activeMon
?.takeIf { it.characterType == DeviceType.BEDevice }
?.let { chara ->
storageRepository.getCharacterBeData(chara.id)
}
?: flowOf<BECharacterData?>(null)
).collectAsState(initial = null)
var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) } var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }
var betaWarning by rememberSaveable { mutableStateOf(true) } var betaWarning by rememberSaveable { mutableStateOf(true) }
var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) } var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) }
var collectedCurrency by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(storageRepository, activeMon, collectedItem) {
withContext(Dispatchers.IO) {
activeMon.value = storageRepository.getActiveCharacter()
if (activeMon.value != null && activeMon.value!!.characterType == DeviceType.BEDevice) {
beData.value = storageRepository.getCharacterBeData(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
} else if (activeMon.value != null && activeMon.value!!.characterType == DeviceType.VBDevice) {
vbData.value = storageRepository.getCharacterVbData(activeMon.value!!.id)
vbSpecialMissions.value = storageRepository.getSpecialMissions(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
}
}
}
LaunchedEffect(true) { LaunchedEffect(true) {
homeScreenController homeScreenController
@ -81,7 +115,7 @@ fun HomeScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "VB Helper", text = stringResource(R.string.home_title),
onScanClick = { onScanClick = {
navController.navigate(NavigationItems.Scan.route) navController.navigate(NavigationItems.Scan.route)
}, },
@ -91,8 +125,7 @@ fun HomeScreen(
) )
} }
) { contentPadding -> ) { contentPadding ->
if (activeMon.value == null || (beData.value == null && vbData.value == null) || transformationHistory.value == null) { if (activeMon == null || (beData == null && vbData == null) || cardIconData == null || transformationHistory.isEmpty()) {
Log.d("TetTet", "Something is null")
Column ( Column (
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
@ -100,35 +133,44 @@ fun HomeScreen(
.fillMaxSize() .fillMaxSize()
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
Text(text = "Nothing to see here") Text(text = stringResource(R.string.adventure_empty_state))
} }
} else { } else {
Log.d("TetTet", "Something is not null") val cardIcon = BitmapData(
if (activeMon.value!!.isBemCard) { bitmap = cardIconData!!.cardIcon,
width = cardIconData!!.cardIconWidth,
height = cardIconData!!.cardIconHeight
)
if (activeMon!!.isBemCard && beData != null) {
BEBEmHomeScreen( BEBEmHomeScreen(
activeMon = activeMon.value!!, activeMon = activeMon!!,
beData = beData.value!!, beData = beData!!,
transformationHistory = transformationHistory.value!!, transformationHistory = transformationHistory,
contentPadding = contentPadding
)
} else if (!activeMon.value!!.isBemCard && activeMon.value!!.characterType == DeviceType.BEDevice) {
BEDiMHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
)
} else {
VBDiMHomeScreen(
activeMon = activeMon.value!!,
vbData = vbData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding, contentPadding = contentPadding,
specialMissions = vbSpecialMissions.value, cardIcon = cardIcon
)
} else if (!activeMon!!.isBemCard && activeMon!!.characterType == DeviceType.BEDevice && beData != null) {
BEDiMHomeScreen(
activeMon = activeMon!!,
beData = beData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
cardIcon = cardIcon
)
} else if (vbData != null) {
VBDiMHomeScreen(
activeMon = activeMon!!,
vbData = vbData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
specialMissions = vbSpecialMissions,
homeScreenController = homeScreenController, homeScreenController = homeScreenController,
onClickCollect = { item -> onClickCollect = { item, currency ->
collectedItem = item collectedItem = item
} collectedCurrency = currency
},
cardIcon = cardIcon
) )
} }
} }
@ -137,8 +179,10 @@ fun HomeScreen(
if (collectedItem != null) { if (collectedItem != null) {
ObtainedItemDialog( ObtainedItemDialog(
obtainedItem = collectedItem!!, obtainedItem = collectedItem!!,
obtainedCurrency = collectedCurrency!!,
onClickDismiss = { onClickDismiss = {
collectedItem = null collectedItem = null
collectedCurrency = null
} }
) )
} }
@ -153,7 +197,7 @@ fun HomeScreen(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = "One of your characters has finished their adventure mission!", text = stringResource(R.string.home_adventure_mission_finished),
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
Button( Button(
@ -164,7 +208,7 @@ fun HomeScreen(
.padding(8.dp) .padding(8.dp)
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text(text = "Dismiss") Text(text = stringResource(R.string.beta_warning_button_dismiss))
} }
} }
} }

View File

@ -4,5 +4,5 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface HomeScreenController { interface HomeScreenController {
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit) fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem) -> Unit) fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit)
} }

View File

@ -2,8 +2,10 @@ package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.Instant import java.time.Instant
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -21,6 +23,7 @@ class HomeScreenControllerImpl(
val adventureCharacters = database val adventureCharacters = database
.adventureDao() .adventureDao()
.getAdventureCharacters() .getAdventureCharacters()
.first()
val finishedAdventureCharacters = adventureCharacters.filter { character -> val finishedAdventureCharacters = adventureCharacters.filter { character ->
character.finishesAdventure <= currentTime character.finishesAdventure <= currentTime
@ -30,37 +33,51 @@ class HomeScreenControllerImpl(
} }
} }
override fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem) -> Unit) { override fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit) {
componentActivity.lifecycleScope.launch { componentActivity.lifecycleScope.launch {
val missionStatus = database
.specialMissionDao()
.getSpecialMission(missionId)
.first()
database database
.specialMissionDao() .specialMissionDao()
.clearSpecialMission(missionId) .clearSpecialMission(missionId)
val randomItem = database if (missionStatus.status == SpecialMission.Status.COMPLETED) {
.itemDao() val randomItem = database
.getAllItems() .itemDao()
.random() .getAllItems()
.first()
.random()
val randomItemAmount = (Random.nextFloat() * 5).roundToInt() val randomItemAmount = (Random.nextFloat() * 5).roundToInt()
database database
.itemDao() .itemDao()
.purchaseItem( .purchaseItem(
itemId = randomItem.id,
itemAmount = randomItemAmount
)
val purchasedItem = ItemDtos.PurchasedItem(
itemId = randomItem.id, itemId = randomItem.id,
itemAmount = randomItemAmount itemName = randomItem.name,
itemDescription = randomItem.description,
itemIcon = randomItem.itemIcon,
itemLength = randomItem.itemLength,
itemAmount = randomItemAmount,
itemType = randomItem.itemType
) )
val purchasedItem = ItemDtos.PurchasedItem( val randomAmount = (2..6).random() * 1000
itemId = randomItem.id, val currentCurrency = application.container.currencyRepository.currencyValue.first()
itemName = randomItem.name, application.container.currencyRepository.setCurrencyValue(currentCurrency + randomAmount)
itemDescription = randomItem.description,
itemIcon = randomItem.itemIcon,
itemLength = randomItem.itemLength,
itemAmount = randomItemAmount,
itemType = randomItem.itemType
)
onCleared(purchasedItem) onCleared(purchasedItem, randomAmount)
} else {
onCleared(null, null)
}
} }
} }
} }

View File

@ -0,0 +1,56 @@
package com.github.nacabaro.vbhelper.screens.homeScreens.dialogs
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
@Composable
fun DeleteSpecialMissionDialog(
onClickDismiss: () -> Unit,
onClickDelete: () -> Unit
) {
Dialog(
onDismissRequest = onClickDismiss
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = stringResource(R.string.home_special_mission_delete_main),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.padding(8.dp))
Row {
Button(
onClick = onClickDismiss,
modifier = Modifier
.padding(8.dp)
) {
Text(text = stringResource(R.string.home_special_mission_delete_dismiss))
}
Button(
onClick = onClickDelete,
modifier = Modifier
.padding(8.dp)
) {
Text(text = stringResource(R.string.home_special_mission_delete_remove))
}
}
}
}
}
}

View File

@ -26,6 +26,7 @@ import java.util.Locale
fun BEBEmHomeScreen( fun BEBEmHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites, activeMon: CharacterDtos.CharacterWithSprites,
beData: BECharacterData, beData: BECharacterData,
cardIcon: BitmapData,
transformationHistory: List<CharacterDtos.TransformationHistory>, transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues contentPadding: PaddingValues
) { ) {
@ -46,6 +47,7 @@ fun BEBEmHomeScreen(
), ),
multiplier = 8, multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
cardIcon = cardIcon,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)

View File

@ -26,6 +26,7 @@ import kotlin.text.format
@Composable @Composable
fun BEDiMHomeScreen( fun BEDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites, activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
beData: BECharacterData, beData: BECharacterData,
transformationHistory: List<CharacterDtos.TransformationHistory>, transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues contentPadding: PaddingValues
@ -45,6 +46,7 @@ fun BEDiMHomeScreen(
width = activeMon.spriteWidth, width = activeMon.spriteWidth,
height = activeMon.spriteHeight height = activeMon.spriteHeight
), ),
cardIcon = cardIcon,
multiplier = 8, multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier modifier = Modifier

View File

@ -10,6 +10,10 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -25,17 +29,23 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import java.util.Locale import java.util.Locale
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.screens.homeScreens.dialogs.DeleteSpecialMissionDialog
@Composable @Composable
fun VBDiMHomeScreen( fun VBDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites, activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
vbData: VBCharacterData, vbData: VBCharacterData,
specialMissions: List<SpecialMissions>, specialMissions: List<SpecialMissions>,
homeScreenController: HomeScreenControllerImpl, homeScreenController: HomeScreenControllerImpl,
transformationHistory: List<CharacterDtos.TransformationHistory>, transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
onClickCollect: (ItemDtos.PurchasedItem) -> Unit onClickCollect: (ItemDtos.PurchasedItem?, Int?) -> Unit
) { ) {
var selectedSpecialMissionId by remember { mutableStateOf<Long>(-1) }
Column( Column(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
@ -51,6 +61,7 @@ fun VBDiMHomeScreen(
width = activeMon.spriteWidth, width = activeMon.spriteWidth,
height = activeMon.spriteHeight height = activeMon.spriteHeight
), ),
cardIcon = cardIcon,
multiplier = 8, multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier modifier = Modifier
@ -65,7 +76,7 @@ fun VBDiMHomeScreen(
ItemDisplay( ItemDisplay(
icon = R.drawable.baseline_vitals_24, icon = R.drawable.baseline_vitals_24,
textValue = activeMon.vitalPoints.toString(), textValue = activeMon.vitalPoints.toString(),
definition = "Vitals", definition = stringResource(R.string.home_vbdim_vitals),
modifier = Modifier modifier = Modifier
.weight(0.5f) .weight(0.5f)
.aspectRatio(1f) .aspectRatio(1f)
@ -74,7 +85,7 @@ fun VBDiMHomeScreen(
ItemDisplay( ItemDisplay(
icon = R.drawable.baseline_trophy_24, icon = R.drawable.baseline_trophy_24,
textValue = activeMon.trophies.toString(), textValue = activeMon.trophies.toString(),
definition = "Trophies", definition = stringResource(R.string.home_vbdim_trophies),
modifier = Modifier modifier = Modifier
.weight(0.5f) .weight(0.5f)
.aspectRatio(1f) .aspectRatio(1f)
@ -89,7 +100,7 @@ fun VBDiMHomeScreen(
ItemDisplay( ItemDisplay(
icon = R.drawable.baseline_mood_24, icon = R.drawable.baseline_mood_24,
textValue = activeMon.mood.toString(), textValue = activeMon.mood.toString(),
definition = "Mood", definition = stringResource(R.string.home_vbdim_mood),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@ -102,7 +113,7 @@ fun VBDiMHomeScreen(
0 -> "${activeMon.transformationCountdown} m" 0 -> "${activeMon.transformationCountdown} m"
else -> "$transformationCountdownInHours h" else -> "$transformationCountdownInHours h"
}, },
definition = "Next timer", definition = stringResource(R.string.home_vbdim_next_timer),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@ -122,7 +133,7 @@ fun VBDiMHomeScreen(
) + " %" // Specify locale ) + " %" // Specify locale
} }
}, },
definition = "Total battle win %", definition = stringResource(R.string.home_vbdim_total_battle_win),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@ -142,7 +153,7 @@ fun VBDiMHomeScreen(
) + " %" // Specify locale ) + " %" // Specify locale
} }
}, },
definition = "Current phase win %", definition = stringResource(R.string.home_vbdim_current_phase_win),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@ -165,7 +176,7 @@ fun VBDiMHomeScreen(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = "Special missions", text = stringResource(R.string.home_vbdim_special_missions),
fontSize = 24.sp fontSize = 24.sp
) )
} }
@ -179,11 +190,28 @@ fun VBDiMHomeScreen(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(8.dp), .padding(8.dp),
) { onClickMission = { missionId ->
homeScreenController selectedSpecialMissionId = missionId
.clearSpecialMission(mission.id, onClickCollect) },
} onClickCollect = {
homeScreenController
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
}
)
} }
} }
} }
if (selectedSpecialMissionId.toInt() != -1) {
DeleteSpecialMissionDialog(
onClickDismiss = {
selectedSpecialMissionId = -1
},
onClickDelete = {
homeScreenController
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
selectedSpecialMissionId = -1
}
)
}
} }

View File

@ -15,6 +15,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
@ -24,8 +25,9 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ChooseCharacterScreen( fun ChooseCharacterScreen(
@ -35,7 +37,7 @@ fun ChooseCharacterScreen(
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db) val storageRepository = StorageRepository(application.container.db, )
val characterList = remember { val characterList = remember {
mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList())
} }
@ -57,7 +59,7 @@ fun ChooseCharacterScreen(
characterList.value = storageRepository.getVBCharacters() characterList.value = storageRepository.getVBCharacters()
} }
else -> { else -> {
characterList.value = storageRepository.getAllCharacters() characterList.value = storageRepository.getAllCharacters().first()
} }
} }
} }
@ -68,7 +70,7 @@ fun ChooseCharacterScreen(
itemsScreenController.applyItem(itemId, selectedCharacter!!) { itemsScreenController.applyItem(itemId, selectedCharacter!!) {
Toast.makeText( Toast.makeText(
application.applicationContext, application.applicationContext,
"Item applied!", application.getString(R.string.choose_character_item_applied),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack() navController.popBackStack()
@ -79,7 +81,7 @@ fun ChooseCharacterScreen(
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Choose character", text = stringResource(R.string.choose_character_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }

View File

@ -0,0 +1,173 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.domain.items.ItemType
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@Composable
fun ItemDialog(
item: ItemDtos.ItemsWithQuantities,
onClickCancel: () -> Unit,
onClickUse: (() -> Unit)? = null,
onClickPurchase: (() -> Unit)? = null,
) {
Dialog(
onDismissRequest = onClickCancel,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
Row {
Box(modifier = Modifier) {
// Background image (full size)
Icon(
painter = painterResource(id = getIconResource(item.itemIcon)),
contentDescription = null,
modifier = Modifier
.size(96.dp)
.align(Alignment.Center)
)
Icon(
painter = painterResource(id = getLengthResource(item.itemLength)),
contentDescription = null,
tint = MaterialTheme.colorScheme.outline,
modifier = Modifier
.size(64.dp) // Set the size of the overlay image
.align(Alignment.BottomEnd) // Align to the top end (top-right corner)
)
}
Column (
modifier = Modifier
.padding(16.dp)
) {
Text(
fontSize = MaterialTheme.typography.titleLarge.fontSize,
text = item.name,
modifier = Modifier
.fillMaxWidth()
)
}
}
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
text = item.description,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
)
if (onClickPurchase != null) {
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.item_dialog_costs_credits,
item.price
),
modifier = Modifier
.fillMaxWidth()
)
}
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.item_dialog_you_have_quantity,
item.quantity
),
modifier = Modifier
.fillMaxWidth()
)
Row (
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
) {
if (onClickUse != null) {
Button(
onClick = onClickUse
) {
Text(stringResource(R.string.item_dialog_use))
}
}
if (onClickPurchase != null) {
Button(
onClick = onClickPurchase
) {
Text(stringResource(R.string.item_dialog_purchase))
}
}
Spacer(modifier = Modifier.size(8.dp))
Button(
onClick = onClickCancel
) {
Text(stringResource(R.string.item_dialog_cancel))
}
}
}
}
}
}
@Composable
@Preview(showBackground = true)
fun PreviewItemDialog() {
VBHelperTheme {
ItemDialog(
item = ItemDtos.ItemsWithQuantities(
name = "AP Training x3 (60 min)",
description = "Boosts AP during training (for 60 minutes)",
itemIcon = R.drawable.baseline_attack_24,
itemLength = R.drawable.baseline_60_min_timer,
quantity = 19,
id = 1,
price = 500,
itemType = ItemType.BEITEM
),
onClickUse = { },
onClickCancel = { }
)
}
}

View File

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

View File

@ -0,0 +1,40 @@
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
}
}

View File

@ -17,6 +17,9 @@ import androidx.compose.ui.Modifier
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ItemsScreen( fun ItemsScreen(
@ -30,14 +33,14 @@ fun ItemsScreen(
Scaffold( Scaffold(
topBar = { topBar = {
Column { Column {
TopBanner("Items") TopBanner(text = stringResource(R.string.items_title))
TabRow( TabRow(
selectedTabIndex = selectedTabItem, selectedTabIndex = selectedTabItem,
modifier = Modifier modifier = Modifier
) { ) {
items.forEachIndexed { index, item -> items.forEachIndexed { index, item ->
Tab( Tab(
text = { Text(item.label) }, text = { Text(text = stringResource(item.label)) },
selected = selectedTabItem == index, selected = selectedTabItem == index,
onClick = { selectedTabItem = index } onClick = { selectedTabItem = index }
) )

View File

@ -11,6 +11,8 @@ import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -52,9 +54,16 @@ class ItemsScreenControllerImpl (
var vbCharacterData: VBCharacterData? = null var vbCharacterData: VBCharacterData? = null
if (characterData.characterType == DeviceType.BEDevice) { if (characterData.characterType == DeviceType.BEDevice) {
beCharacterData = database.userCharacterDao().getBeData(characterId) beCharacterData = database
.userCharacterDao()
.getBeData(characterId)
.firstOrNull()
} else if (characterData.characterType == DeviceType.VBDevice) { } else if (characterData.characterType == DeviceType.VBDevice) {
vbCharacterData = database.userCharacterDao().getVbData(characterId) vbCharacterData = database
.userCharacterDao()
.getVbData(characterId)
.firstOrNull()
} }
if ( if (
@ -153,33 +162,35 @@ class ItemsScreenControllerImpl (
ItemTypes.Win4.id -> 4 ItemTypes.Win4.id -> 4
else -> 0 else -> 0
} }
val specialMissionSlot = when (itemIcon) {
ItemTypes.Step8k.id -> 0
ItemTypes.Step4k.id -> 0
ItemTypes.Vitals1000.id -> 1
ItemTypes.Vitals250.id -> 1
ItemTypes.Battle20.id -> 2
ItemTypes.Battle5.id -> 2
ItemTypes.Win10.id -> 3
ItemTypes.Win4.id -> 3
else -> 0
}
val availableSpecialMissions = database val availableSpecialMissions = database
.userCharacterDao() .userCharacterDao()
.getSpecialMissions(characterId) .getSpecialMissions(characterId)
.first()
var firstUnavailableMissionSlot: Long = 0 var newSpecialMission = availableSpecialMissions[specialMissionSlot]
var watchId = 0 newSpecialMission = SpecialMissions(
id = newSpecialMission.id,
for ((index, mission) in availableSpecialMissions.withIndex()) { characterId = newSpecialMission.characterId,
if (
mission.status == SpecialMission.Status.UNAVAILABLE
) {
firstUnavailableMissionSlot = mission.id
watchId = index + 1
}
}
val newSpecialMission = SpecialMissions(
id = firstUnavailableMissionSlot,
characterId = characterId,
missionType = specialMissionType,
goal = specialMissionGoal, goal = specialMissionGoal,
timeLimitInMinutes = itemLength, watchId = newSpecialMission.watchId,
watchId = watchId,
status = SpecialMission.Status.AVAILABLE,
progress = 0, progress = 0,
timeElapsedInMinutes = 0 status = SpecialMission.Status.AVAILABLE,
timeElapsedInMinutes = 0,
timeLimitInMinutes = itemLength,
missionType = specialMissionType
) )
database database

View File

@ -1,25 +1,37 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen package com.github.nacabaro.vbhelper.screens.itemsScreen
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.ItemsRepository import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ItemsStore( fun ItemsStore(
@ -27,45 +39,102 @@ fun ItemsStore(
) { ) {
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val itemsRepository = ItemsRepository(application.container.db) val itemsRepository = ItemsRepository(application.container.db)
val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) } val myItems by itemsRepository.getAllItems().collectAsState(emptyList())
var selectedElementIndex by remember { mutableStateOf<Int?>(null) } var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(itemsRepository) { val currencyRepository = application.container.currencyRepository
withContext(Dispatchers.IO) { val currentCurrency = currencyRepository.currencyValue.collectAsState(0)
myItems.value = itemsRepository.getAllItems()
}
}
if (myItems.value.isEmpty()) { val scope = rememberCoroutineScope()
Text("No items")
} else { if (myItems.isEmpty()) {
LazyVerticalGrid( Column(
columns = GridCells.Fixed(3), verticalArrangement = Arrangement.Center,
modifier = Modifier horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) { ) {
items(myItems.value) { index -> Text(stringResource(R.string.items_no_items))
ItemElement( }
itemIcon = getIconResource(index.itemIcon), } else {
lengthIcon = getLengthResource(index.itemLength), Column() {
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text(
text = stringResource(
R.string.items_store_credits,
currentCurrency.value
),
modifier = Modifier modifier = Modifier
.padding(8.dp), .padding(8.dp)
onClick = {
selectedElementIndex = myItems.value.indexOf(index)
}
) )
} }
}
if (selectedElementIndex != null) { LazyVerticalGrid(
ItemDialog( columns = GridCells.Fixed(3),
name = myItems.value[selectedElementIndex!!].name, modifier = Modifier
description = myItems.value[selectedElementIndex!!].description, ) {
itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon), items(myItems) { index ->
lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength), ItemElement(
amount = myItems.value[selectedElementIndex!!].quantity, item = index,
onClickUse = { }, modifier = Modifier
onClickCancel = { selectedElementIndex = null } .padding(8.dp),
) onClick = {
selectedElementIndex = myItems.indexOf(index)
}
)
}
}
} }
} }
if (selectedElementIndex != null) {
ItemDialog(
item = myItems[selectedElementIndex!!],
onClickPurchase = {
scope.launch {
Toast.makeText(
application.applicationContext,
application.getString(
purchaseItem(
application.container.db,
myItems[selectedElementIndex!!],
currencyRepository
)
),
Toast.LENGTH_SHORT
).show()
}
},
onClickCancel = { selectedElementIndex = null }
)
}
}
suspend fun purchaseItem(
db: AppDatabase,
item: ItemDtos.ItemsWithQuantities,
currencyRepository: CurrencyRepository
): Int {
return if (currencyRepository.currencyValue.first() < item.price) {
R.string.items_not_enough_credits
} else {
db
.itemDao()
.purchaseItem(
item.id,
1
)
currencyRepository
.setCurrencyValue(
currencyRepository.currencyValue.first() - item.price
)
R.string.items_purchase_success
}
} }

View File

@ -9,7 +9,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -17,14 +17,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.ItemsRepository import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.Dispatchers import com.github.nacabaro.vbhelper.R
import kotlinx.coroutines.withContext
@Composable @Composable
fun MyItems( fun MyItems(
@ -32,36 +32,30 @@ fun MyItems(
) { ) {
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val itemsRepository = ItemsRepository(application.container.db) val itemsRepository = ItemsRepository(application.container.db)
val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) } val myItems by itemsRepository.getUserItems().collectAsState(emptyList())
var selectedElementIndex by remember { mutableStateOf<Int?>(null) } var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(itemsRepository) { if (myItems.isEmpty()) {
withContext(Dispatchers.IO) {
myItems.value = itemsRepository.getUserItems()
}
}
if (myItems.value.isEmpty()) {
Column( Column(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Text("No items") Text(stringResource(R.string.items_no_items))
} }
} else { } else {
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
modifier = Modifier modifier = Modifier
) { ) {
items(myItems.value) { index -> items(myItems) { index ->
ItemElement( ItemElement(
itemIcon = getIconResource(index.itemIcon), item = index,
lengthIcon = getLengthResource(index.itemLength),
modifier = Modifier modifier = Modifier
.padding(8.dp), .padding(8.dp),
onClick = { onClick = {
selectedElementIndex = myItems.value.indexOf(index) selectedElementIndex = myItems.indexOf(index)
} }
) )
} }
@ -69,11 +63,7 @@ fun MyItems(
if (selectedElementIndex != null) { if (selectedElementIndex != null) {
ItemDialog( ItemDialog(
name = myItems.value[selectedElementIndex!!].name, item = myItems[selectedElementIndex!!],
description = myItems.value[selectedElementIndex!!].description,
itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon),
lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength),
amount = myItems.value[selectedElementIndex!!].quantity,
onClickUse = { onClickUse = {
navController navController
.navigate( .navigate(
@ -81,7 +71,7 @@ fun MyItems(
.ApplyItem.route .ApplyItem.route
.replace( .replace(
"{itemId}", "{itemId}",
myItems.value[selectedElementIndex!!].id.toString() myItems[selectedElementIndex!!].id.toString()
) )
) )
selectedElementIndex = null selectedElementIndex = null

View File

@ -19,10 +19,13 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ObtainedItemDialog( fun ObtainedItemDialog(
obtainedItem: ItemDtos.PurchasedItem, obtainedItem: ItemDtos.PurchasedItem,
obtainedCurrency: Int,
onClickDismiss: () -> Unit onClickDismiss: () -> Unit
) { ) {
Dialog( Dialog(
@ -81,17 +84,32 @@ fun ObtainedItemDialog(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize, fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily, fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have obtained ${obtainedItem.itemAmount} of this item", text = stringResource(
R.string.obtained_item_you_have,
obtainedItem.itemAmount
),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(5.dp) .padding(top = 4.dp)
)
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.obtained_item_you_also_got_credits,
obtainedCurrency
),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp)
) )
Button( Button(
onClick = onClickDismiss, onClick = onClickDismiss,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text(text = "Dismiss") Text(text = stringResource(R.string.obtained_item_dismiss))
} }
} }
} }

View File

@ -0,0 +1,80 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
navController: NavController
) {
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.scan_title),
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
ScanButton(
text = stringResource(R.string.scan_vb_to_app),
disabled = onClickRead == null,
onClick = onClickRead?: { },
)
Spacer(modifier = Modifier.height(16.dp))
ScanButton(
text = stringResource(R.string.scan_app_to_vb),
disabled = onClickWrite == null,
onClick = onClickWrite?: { },
)
}
}
}
@Composable
fun ScanButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
disabled: Boolean = false,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = !disabled,
) {
Text(
text = text,
fontSize = 16.sp,
modifier = Modifier
.padding(4.dp)
)
}
}

View File

@ -1,44 +1,31 @@
package com.github.nacabaro.vbhelper.screens.scanScreen package com.github.nacabaro.vbhelper.screens.scanScreen
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard import com.github.nacabaro.vbhelper.screens.scanScreen.screens.ReadingScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.WritingScreen
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.source.isMissingSecrets import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.github.nacabaro.vbhelper.R
const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER" const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER"
@ -55,8 +42,6 @@ fun ScanScreen(
val storageRepository = StorageRepository(application.container.db) val storageRepository = StorageRepository(application.container.db)
var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) } var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) }
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
val context = LocalContext.current val context = LocalContext.current
LaunchedEffect(storageRepository) { LaunchedEffect(storageRepository) {
@ -73,143 +58,33 @@ fun ScanScreen(
} }
} }
var readingScreen by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) } var writingScreen by remember { mutableStateOf(false) }
var cardSelectScreen by remember { mutableStateOf(false) } var readingScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
DisposableEffect(readingScreen) { if (writingScreen && nfcCharacter != null && characterId != null) {
if(readingScreen) { WritingScreen(
scanScreenController.registerActivityLifecycleListener( scanScreenController = scanScreenController,
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER, nfcCharacter = nfcCharacter!!,
object: ActivityLifecycleListener { characterId = characterId,
override fun onPause() { onComplete = {
scanScreenController.cancelRead()
}
override fun onResume() {
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
}
)
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
DisposableEffect(writingScreen, isDoneSendingCard) {
if (writingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
}
)
}
if (secrets != null && nfcCharacter != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
onDispose {
if(writingScreen) {
scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false
navController.navigate(NavigationItems.Home.route)
} else if (isDoneSendingCard && isDoneWritingCharacter) {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
LaunchedEffect(storageRepository) {
withContext(Dispatchers.IO) {
storageRepository
.deleteCharacter(characterId!!)
}
}
}
if (readingScreen) {
ReadingCharacterScreen("Reading character") {
readingScreen = false
scanScreenController.cancelRead()
}
} else if (writingScreen) {
if (!isDoneSendingCard) {
ReadingCharacterScreen("Sending card") {
writingScreen = false writingScreen = false
scanScreenController.cancelRead() navController.navigate(NavigationItems.Home.route)
} },
} else if (!isDoneWritingCharacter) { onCancel = {
ReadingCharacterScreen("Writing character") {
isDoneSendingCard = false
writingScreen = false writingScreen = false
scanScreenController.cancelRead() navController.navigate(NavigationItems.Home.route)
} }
} )
} else if (cardSelectScreen) { } else if (readingScreen) {
ChooseCard( ReadingScreen(
cards = cardsRead!!, scanScreenController = scanScreenController,
onCardSelected = { card -> onCancel = {
cardSelectScreen = false readingScreen = false
scanScreenController.flushCharacter(card.id) navController.navigate(NavigationItems.Home.route)
},
onComplete = {
readingScreen = false
navController.navigate(NavigationItems.Home.route)
} }
) )
} else { } else {
@ -219,9 +94,9 @@ fun ScanScreen(
else -> { else -> {
{ {
if(secrets == null) { if(secrets == null) {
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show() Toast.makeText(context, context.getString(R.string.scan_secrets_not_initialized), Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) { } else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show() Toast.makeText(context, context.getString(R.string.scan_secrets_not_imported), Toast.LENGTH_SHORT).show()
} else { } else {
readingScreen = true // kicks off nfc adapter in DisposableEffect readingScreen = true // kicks off nfc adapter in DisposableEffect
} }
@ -233,9 +108,9 @@ fun ScanScreen(
else -> { else -> {
{ {
if(secrets == null) { if(secrets == null) {
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show() Toast.makeText(context, context.getString(R.string.scan_secrets_not_initialized), Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) { } else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show() Toast.makeText(context, context.getString(R.string.scan_secrets_not_imported), Toast.LENGTH_SHORT).show()
} else { } else {
writingScreen = true // kicks off nfc adapter in DisposableEffect writingScreen = true // kicks off nfc adapter in DisposableEffect
} }
@ -247,66 +122,8 @@ fun ScanScreen(
} }
} }
@Composable
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
navController: NavController
) {
Scaffold(
topBar = {
TopBanner(
text = "Scan a Vital Bracelet",
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
ScanButton(
text = "Vital Bracelet to App",
disabled = onClickRead == null,
onClick = onClickRead?: { },
)
Spacer(modifier = Modifier.height(16.dp))
ScanButton(
text = "App to Vital Bracelet",
disabled = onClickWrite == null,
onClick = onClickWrite?: { },
)
}
}
}
@Composable
fun ScanButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
disabled: Boolean = false,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = !disabled,
) {
Text(
text = text,
fontSize = 16.sp,
modifier = Modifier
.padding(4.dp)
)
}
}
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun ScanScreenPreview() { fun ScanScreenPreview() {

View File

@ -25,6 +25,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
class ScanScreenControllerImpl( class ScanScreenControllerImpl(
override val secretsFlow: Flow<Secrets>, override val secretsFlow: Flow<Secrets>,
@ -38,7 +39,7 @@ class ScanScreenControllerImpl(
init { init {
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity) val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity)
if (maybeNfcAdapter == null) { if (maybeNfcAdapter == null) {
Toast.makeText(componentActivity, "No NFC on device!", Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_no_nfc_on_device), Toast.LENGTH_SHORT).show()
} }
nfcAdapter = maybeNfcAdapter nfcAdapter = maybeNfcAdapter
checkSecrets() checkSecrets()
@ -94,7 +95,7 @@ class ScanScreenControllerImpl(
val nfcData = NfcA.get(tag) val nfcData = NfcA.get(tag)
if (nfcData == null) { if (nfcData == null) {
componentActivity.runOnUiThread { componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Tag detected is not VB", Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_tag_not_vb), Toast.LENGTH_SHORT).show()
} }
} }
nfcData.connect() nfcData.connect()
@ -112,7 +113,7 @@ class ScanScreenControllerImpl(
componentActivity.lifecycleScope.launch(Dispatchers.IO) { componentActivity.lifecycleScope.launch(Dispatchers.IO) {
if(secretsFlow.stateIn(componentActivity.lifecycleScope).value.isMissingSecrets()) { if(secretsFlow.stateIn(componentActivity.lifecycleScope).value.isMissingSecrets()) {
componentActivity.runOnUiThread { componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Missing Secrets. Go to settings and import Vital Arena APK", Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_missing_secrets), Toast.LENGTH_SHORT).show()
} }
} }
} }
@ -135,10 +136,10 @@ class ScanScreenControllerImpl(
tagCommunicator.sendCharacter(castNfcCharacter) tagCommunicator.sendCharacter(castNfcCharacter)
} }
onComplete.invoke() onComplete.invoke()
"Sent character successfully!" componentActivity.getString(R.string.scan_sent_character_success)
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e("TAG", e.stackTraceToString()) Log.e("TAG", e.stackTraceToString())
"Whoops" componentActivity.getString(R.string.scan_error_generic)
} }
} }
} }
@ -151,13 +152,13 @@ class ScanScreenControllerImpl(
handleTag(secrets) { tagCommunicator -> handleTag(secrets) { tagCommunicator ->
tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId) tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId)
onComplete.invoke() onComplete.invoke()
"Sent DIM successfully!" componentActivity.getString(R.string.scan_sent_dim_success)
} }
} }
// EXTRACTED DIRECTLY FROM EXAMPLE APP // EXTRACTED DIRECTLY FROM EXAMPLE APP
private fun showWirelessSettings() { private fun showWirelessSettings() {
Toast.makeText(componentActivity, "NFC must be enabled", Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_nfc_must_be_enabled), Toast.LENGTH_SHORT).show()
componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS)) componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
} }

View File

@ -1,13 +1,11 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
@ -152,15 +150,13 @@ class FromNfcConverter (
nfcCharacter: NfcCharacter, nfcCharacter: NfcCharacter,
cardData: Card cardData: Card
) { ) {
val currentCardProgress = CardProgress(
cardId = cardData.id,
currentStage = nfcCharacter.nextAdventureMissionStage.toInt(),
unlocked = nfcCharacter.nextAdventureMissionStage.toInt() > cardData.stageCount
)
database database
.cardProgressDao() .cardProgressDao()
.updateDimProgress(currentCardProgress) .updateCardProgress(
currentStage = nfcCharacter.nextAdventureMissionStage.toInt(),
cardId = cardData.id,
unlocked = nfcCharacter.nextAdventureMissionStage.toInt() > cardData.stageCount,
)
} }
@ -252,10 +248,11 @@ class FromNfcConverter (
) { ) {
val vitalsHistoryWatch = nfcCharacter.vitalHistory val vitalsHistoryWatch = nfcCharacter.vitalHistory
val vitalsHistory = vitalsHistoryWatch.map { historyElement -> val vitalsHistory = vitalsHistoryWatch.map { historyElement ->
Log.d("VitalsHistory", "${historyElement.year.toInt()} ${historyElement.month.toInt()} ${historyElement.day.toInt()}") val year = if (historyElement.year.toInt() !in 2021 .. 2035) 0 else historyElement.year.toInt()
VitalsHistory( VitalsHistory(
charId = characterId, charId = characterId,
year = historyElement.year.toInt(), year = year,
month = historyElement.month.toInt(), month = historyElement.month.toInt(),
day = historyElement.day.toInt(), day = historyElement.day.toInt(),
vitalPoints = historyElement.vitalsGained.toInt() vitalPoints = historyElement.vitalsGained.toInt()

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.icu.util.Calendar import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter import com.github.cfogrady.vbnfc.be.BENfcCharacter
@ -13,6 +14,7 @@ import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.flow.first
import java.util.Date import java.util.Date
class ToNfcConverter( class ToNfcConverter(
@ -37,14 +39,10 @@ class ToNfcConverter(
.characterDao() .characterDao()
.getCharacterInfo(userCharacter.charId) .getCharacterInfo(userCharacter.charId)
val currentCardStage = database
.cardProgressDao()
.getCardProgress(characterInfo.cardId)
return if (userCharacter.characterType == DeviceType.BEDevice) return if (userCharacter.characterType == DeviceType.BEDevice)
nfcToBENfc(characterId, characterInfo, currentCardStage, userCharacter) nfcToBENfc(characterId, characterInfo, userCharacter)
else else
nfcToVBNfc(characterId, characterInfo, currentCardStage, userCharacter) nfcToVBNfc(characterId, characterInfo, userCharacter)
} }
@ -52,12 +50,12 @@ class ToNfcConverter(
private suspend fun nfcToVBNfc( private suspend fun nfcToVBNfc(
characterId: Long, characterId: Long,
characterInfo: CharacterDtos.CardCharacterInfo, characterInfo: CharacterDtos.CardCharacterInfo,
currentCardStage: Int,
userCharacter: UserCharacter userCharacter: UserCharacter
): VBNfcCharacter { ): VBNfcCharacter {
val vbData = database val vbData = database
.userCharacterDao() .userCharacterDao()
.getVbData(characterId) .getVbData(characterId)
.first()
val paddedTransformationArray = generateTransformationHistory(characterId, 9) val paddedTransformationArray = generateTransformationHistory(characterId, 9)
@ -69,7 +67,7 @@ class ToNfcConverter(
stage = characterInfo.stage.toByte(), stage = characterInfo.stage.toByte(),
attribute = characterInfo.attribute, attribute = characterInfo.attribute,
ageInDays = userCharacter.ageInDays.toByte(), ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = currentCardStage.toByte(), nextAdventureMissionStage = characterInfo.currentStage.toByte(),
mood = userCharacter.mood.toByte(), mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(), vitalPoints = userCharacter.vitalPoints.toUShort(),
transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(), transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(),
@ -100,6 +98,7 @@ class ToNfcConverter(
val cardData = database val cardData = database
.cardDao() .cardDao()
.getCardByCharacterId(userCharacter.id) .getCardByCharacterId(userCharacter.id)
.first()
val appReserved = Array<UShort>(3) { val appReserved = Array<UShort>(3) {
0u 0u
@ -118,6 +117,7 @@ class ToNfcConverter(
val specialMissions = database val specialMissions = database
.userCharacterDao() .userCharacterDao()
.getSpecialMissions(characterId) .getSpecialMissions(characterId)
.first()
val watchSpecialMissions = specialMissions.map { val watchSpecialMissions = specialMissions.map {
SpecialMission( SpecialMission(
@ -172,12 +172,12 @@ class ToNfcConverter(
private suspend fun nfcToBENfc( private suspend fun nfcToBENfc(
characterId: Long, characterId: Long,
characterInfo: CharacterDtos.CardCharacterInfo, characterInfo: CharacterDtos.CardCharacterInfo,
currentCardStage: Int,
userCharacter: UserCharacter userCharacter: UserCharacter
): BENfcCharacter { ): BENfcCharacter {
val beData = database val beData = database
.userCharacterDao() .userCharacterDao()
.getBeData(characterId) .getBeData(characterId)
.first()
val paddedTransformationArray = generateTransformationHistory(characterId) val paddedTransformationArray = generateTransformationHistory(characterId)
@ -187,7 +187,7 @@ class ToNfcConverter(
stage = characterInfo.stage.toByte(), stage = characterInfo.stage.toByte(),
attribute = characterInfo.attribute, attribute = characterInfo.attribute,
ageInDays = userCharacter.ageInDays.toByte(), ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = currentCardStage.toByte(), nextAdventureMissionStage = characterInfo.currentStage.toByte(),
mood = userCharacter.mood.toByte(), mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(), vitalPoints = userCharacter.vitalPoints.toUShort(),
itemEffectMentalStateValue = beData.itemEffectMentalStateValue.toByte(), itemEffectMentalStateValue = beData.itemEffectMentalStateValue.toByte(),
@ -240,19 +240,27 @@ class ToNfcConverter(
): Array<NfcCharacter.Transformation> { ): Array<NfcCharacter.Transformation> {
val transformationHistory = database val transformationHistory = database
.userCharacterDao() .userCharacterDao()
.getTransformationHistory(characterId)!! .getTransformationHistory(characterId)
.first()
.map { .map {
val date = Date(it.transformationDate) val date = Date(it.transformationDate)
val calendar = android.icu.util.GregorianCalendar() val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC"))
calendar.time = date calendar.time = date
Log.d(
"TransformationHistory",
"Year: ${calendar.get(Calendar.YEAR)}, " +
"Month: ${calendar.get(Calendar.MONTH) + 1}, " +
"Day: ${calendar.get(Calendar.DAY_OF_MONTH)}"
)
NfcCharacter.Transformation( NfcCharacter.Transformation(
toCharIndex = it.monIndex.toUByte(), toCharIndex = it.monIndex.toUByte(),
year = calendar year = calendar
.get(Calendar.YEAR) .get(Calendar.YEAR)
.toUShort(), .toUShort(),
month = calendar month = (calendar
.get(Calendar.MONTH) .get(Calendar.MONTH) + 1)
.toUByte(), .toUByte(),
day = calendar day = calendar
.get(Calendar.DAY_OF_MONTH) .get(Calendar.DAY_OF_MONTH)

View File

@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.screens.scanScreen package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -10,17 +10,22 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ReadingCharacterScreen( fun ActionScreen(
topBannerText: String, topBannerText: String,
onClickCancel: () -> Unit, onClickCancel: () -> Unit,
) { ) {
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner(topBannerText) TopBanner(
text = topBannerText,
onBackClick = onClickCancel
)
} }
) { innerPadding -> ) { innerPadding ->
Column ( Column (
@ -30,12 +35,12 @@ fun ReadingCharacterScreen(
.padding(innerPadding) .padding(innerPadding)
.fillMaxSize() .fillMaxSize()
) { ) {
Text("Place your Vital Bracelet near the reader...") Text(stringResource(R.string.action_place_near_reader))
Button( Button(
onClick = onClickCancel, onClick = onClickCancel,
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) { ) {
Text("Cancel") Text(stringResource(R.string.action_cancel))
} }
} }
} }

View File

@ -0,0 +1,61 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadCharacterScreen(
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.read_character_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Text(
text = stringResource(R.string.read_character_prepare_device),
textAlign = TextAlign.Center
)
Text(
text = stringResource(R.string.read_character_go_to_connect),
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.padding(8.dp)
)
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.read_character_confirm))
}
}
}
}

View File

@ -0,0 +1,112 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadingScreen(
scanScreenController: ScanScreenController,
onCancel: () -> Unit,
onComplete: () -> Unit
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
var readingScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var cardSelectScreen by remember { mutableStateOf(false) }
DisposableEffect(readingScreen) {
if(readingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object: ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
}
)
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false
onComplete()
}
if (!readingScreen) {
ReadCharacterScreen(
onClickConfirm = {
readingScreen = true
},
onClickCancel = {
onCancel()
}
)
}
if (readingScreen) {
ActionScreen(topBannerText = stringResource(R.string.reading_character_title),) {
readingScreen = false
scanScreenController.cancelRead()
onCancel()
}
} else if (cardSelectScreen) {
ChooseCard(
cards = cardsRead!!,
onCardSelected = { card ->
cardSelectScreen = false
scanScreenController.flushCharacter(card.id)
}
)
}
}

View File

@ -0,0 +1,142 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable
fun WriteCardScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.write_card_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(
R.string.write_card_icon_description
),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text(stringResource(R.string.write_card_device_ready))
Text(
stringResource(
R.string.write_card_required_card,
cardDetails.name
)
)
}
}
}
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.write_card_confirm))
}
}
}
}

View File

@ -0,0 +1,138 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable
fun WriteCharacterScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.write_character_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(R.string.write_character_icon_description),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text(stringResource(R.string.write_character_success))
Text(stringResource(R.string.write_character_wait_ready))
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.write_character_confirm))
}
}
}
}

View File

@ -0,0 +1,142 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun WritingScreen(
scanScreenController: ScanScreenController,
characterId: Long,
nfcCharacter: NfcCharacter,
onComplete: () -> Unit,
onCancel: () -> Unit,
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
var writing by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var writingConfirmScreen by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
DisposableEffect(writing) {
if (writing) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
)
if (secrets != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
onDispose {
if (writing) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (!writingScreen) {
writing = false
WriteCardScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingScreen = true
}
)
} else if (!isDoneSendingCard) {
writing = true
ActionScreen( topBannerText = stringResource(R.string.sending_card_title)) {
scanScreenController.cancelRead()
onCancel()
}
} else if (!writingConfirmScreen) {
writing = false
WriteCharacterScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingConfirmScreen = true
}
)
} else if (!isDoneWritingCharacter) {
writing = true
ActionScreen(topBannerText = stringResource(R.string.writing_character_action_title)) {
isDoneSendingCard = false
scanScreenController.cancelRead()
onCancel()
}
}
var completedWriting by remember { mutableStateOf(false) }
LaunchedEffect(isDoneSendingCard, isDoneWritingCharacter) {
withContext(Dispatchers.IO) {
if (isDoneSendingCard && isDoneWritingCharacter) {
storageRepository
.deleteCharacter(characterId)
completedWriting = true
}
}
}
if (completedWriting) {
onComplete()
}
}

View File

@ -6,8 +6,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CreditsScreen( fun CreditsScreen(
@ -16,7 +18,7 @@ fun CreditsScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "Credits", text = stringResource(R.string.credits_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@ -29,13 +31,13 @@ fun CreditsScreen(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
SettingsSection("Reverse engineering") SettingsSection(stringResource(R.string.credits_section_reverse_engineering))
SettingsEntry(title = "cyanic", description = "Reversed the firmware and helped us during development.") { } SettingsEntry(title = "cyanic", description = stringResource(R.string.credits_cyanic_description)) { }
SettingsSection("Application development") SettingsSection(stringResource(R.string.credits_section_app_development))
SettingsEntry(title = "cfogrady", description = "Developed vb-lib-nfc and part of this application.") { } SettingsEntry(title = "cfogrady", description = stringResource(R.string.credits_cfogrady_description)) { }
SettingsEntry(title = "nacabaro", description = "Developed this application.") { } SettingsEntry(title = "nacabaro", description = stringResource(R.string.credits_nacabaro_description)) { }
SettingsEntry(title = "lightheel", description = "Developing the battling part for this application, including server. Still in the works.") { } SettingsEntry(title = "lightheel", description = stringResource(R.string.credits_lightheel_description)) { }
SettingsEntry(title = "shvstrz", description = "Designing the app icon in SVG.") { } SettingsEntry(title = "shvstrz", description = stringResource(R.string.credits_shvstrz_description)) { }
} }
} }
} }

View File

@ -1,5 +1,7 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -13,22 +15,28 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
navController: NavController, navController: NavController,
settingsScreenController: SettingsScreenControllerImpl, settingsScreenController: SettingsScreenControllerImpl,
) { ) {
Scaffold ( val context = LocalContext.current
Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Settings", text = stringResource(R.string.settings_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@ -37,31 +45,57 @@ fun SettingsScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) { contentPadding -> ) { contentPadding ->
Column ( Column(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
SettingsSection("NFC Communication") SettingsSection(title = stringResource(R.string.settings_section_nfc))
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") { SettingsEntry(
title = stringResource(R.string.settings_import_apk_title),
description = stringResource(R.string.settings_import_apk_desc)
) {
settingsScreenController.onClickImportApk() settingsScreenController.onClickImportApk()
} }
SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import card", description = "Import DiM/BEm card file") { SettingsSection(title = stringResource(R.string.settings_section_dim_bem))
SettingsEntry(
title = stringResource(R.string.settings_import_card_title),
description = stringResource(R.string.settings_import_card_desc)
) {
settingsScreenController.onClickImportCard() settingsScreenController.onClickImportCard()
} }
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
SettingsSection("About and credits") SettingsSection(title = stringResource(R.string.settings_section_about))
SettingsEntry(title = "Credits", description = "Credits") { SettingsEntry(
title = stringResource(R.string.settings_credits_title),
description = stringResource(R.string.settings_credits_desc)
) {
navController.navigate(NavigationItems.Credits.route) navController.navigate(NavigationItems.Credits.route)
} }
SettingsEntry(title = "About", description = "About") { } SettingsEntry(
SettingsSection("Data management") title = stringResource(R.string.settings_about_title),
SettingsEntry(title = "Export data", description = "Export application database") { description = stringResource(R.string.settings_about_desc)
) {
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse("https://github.com/nacabaro/vbhelper/")
)
context.startActivity(browserIntent)
}
SettingsSection(title = stringResource(R.string.settings_section_data))
SettingsEntry(
title = stringResource(R.string.settings_export_data_title),
description = stringResource(R.string.settings_export_data_desc)
) {
settingsScreenController.onClickOpenDirectory() settingsScreenController.onClickOpenDirectory()
} }
SettingsEntry(title = "Import data", description = "Import application database") { SettingsEntry(
title = stringResource(R.string.settings_import_data_title),
description = stringResource(R.string.settings_import_data_desc)
) {
settingsScreenController.onClickImportDatabase() settingsScreenController.onClickImportDatabase()
} }
} }
@ -93,9 +127,9 @@ fun SettingsEntry(
fun SettingsSection( fun SettingsSection(
title: String title: String
) { ) {
Box ( Box(
modifier = Modifier modifier = Modifier
.padding(start = 16.dp) .padding(start = 16.dp, top = 16.dp, bottom = 4.dp)
) { ) {
Text( Text(
text = title, text = title,
@ -104,4 +138,4 @@ fun SettingsSection(
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary
) )
} }
} }

View File

@ -3,36 +3,24 @@ package com.github.nacabaro.vbhelper.screens.settingsScreen
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import android.net.Uri import android.net.Uri
import android.provider.OpenableColumns
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import com.github.cfogrady.vb.dim.card.BemCard
import com.github.cfogrady.vb.dim.card.DimCard
import com.github.cfogrady.vb.dim.card.DimReader
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.characters.Sprite import com.github.nacabaro.vbhelper.screens.settingsScreen.controllers.CardImportController
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.screens.settingsScreen.controllers.DatabaseManagementController
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.source.ApkSecretsImporter import com.github.nacabaro.vbhelper.source.ApkSecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsImporter import com.github.nacabaro.vbhelper.source.SecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsRepository import com.github.nacabaro.vbhelper.source.SecretsRepository
import com.github.nacabaro.vbhelper.source.proto.Secrets import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import java.io.File
import java.io.InputStream
import java.io.OutputStream
class SettingsScreenControllerImpl( class SettingsScreenControllerImpl(
private val context: ComponentActivity, private val context: ComponentActivity,
): SettingsScreenController { ): SettingsScreenController {
private val roomDbName = "internalDb"
private val filePickerLauncher: ActivityResultLauncher<String> private val filePickerLauncher: ActivityResultLauncher<String>
private val filePickerOpenerLauncher: ActivityResultLauncher<Array<String>> private val filePickerOpenerLauncher: ActivityResultLauncher<Array<String>>
private val filePickerApk: ActivityResultLauncher<Array<String>> private val filePickerApk: ActivityResultLauncher<Array<String>>
@ -41,13 +29,17 @@ class SettingsScreenControllerImpl(
private val application = context.applicationContext as VBHelper private val application = context.applicationContext as VBHelper
private val secretsRepository: SecretsRepository = application.container.dataStoreSecretsRepository private val secretsRepository: SecretsRepository = application.container.dataStoreSecretsRepository
private val database: AppDatabase = application.container.db private val database: AppDatabase = application.container.db
private val databaseManagementController = DatabaseManagementController(
componentActivity = context,
application = application
)
init { init {
filePickerLauncher = context.registerForActivityResult( filePickerLauncher = context.registerForActivityResult(
ActivityResultContracts.CreateDocument("application/octet-stream") ActivityResultContracts.CreateDocument("application/octet-stream")
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
exportDatabase(uri) databaseManagementController.exportDatabase(uri)
} else { } else {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT) Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT)
@ -60,7 +52,7 @@ class SettingsScreenControllerImpl(
ActivityResultContracts.OpenDocument() ActivityResultContracts.OpenDocument()
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
importDatabase(uri) databaseManagementController.importDatabase(uri)
} else { } else {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show() Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show()
@ -113,119 +105,10 @@ class SettingsScreenControllerImpl(
context.lifecycleScope.launch(Dispatchers.IO) { context.lifecycleScope.launch(Dispatchers.IO) {
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
val inputStream = contentResolver.openInputStream(uri) val inputStream = contentResolver.openInputStream(uri)
inputStream.use { fileReader -> inputStream.use { fileReader ->
val dimReader = DimReader() val cardImportController = CardImportController(database)
val card = dimReader.readCard(fileReader, false) cardImportController.importCard(fileReader)
val cardModel = Card(
cardId = card.header.dimId,
logo = card.spriteData.sprites[0].pixelData,
name = card.spriteData.text, // TODO Make user write card name
stageCount = card.adventureLevels.levels.size,
logoHeight = card.spriteData.sprites[0].height,
logoWidth = card.spriteData.sprites[0].width,
isBEm = card is BemCard
)
val dimId = database
.cardDao()
.insertNewCard(cardModel)
val cardProgress = CardProgress(
cardId = dimId,
currentStage = 0,
unlocked = false
)
database
.cardProgressDao()
.updateDimProgress(cardProgress)
val characters = card
.characterStats
.characterEntries
var spriteCounter = when (card is BemCard) {
true -> 54
false -> 10
}
val domainCharacters = mutableListOf<Character>()
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(
Character(
dimId = dimId,
spriteId = spriteId,
monIndex = index,
name = 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())
} }
inputStream?.close() inputStream?.close()
@ -235,100 +118,6 @@ class SettingsScreenControllerImpl(
} }
} }
private fun exportDatabase(destinationUri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) {
try {
val dbFile = File(context.getDatabasePath(roomDbName).absolutePath)
if (!dbFile.exists()) {
throw IllegalStateException("Database file does not exist!")
}
application.container.db.close()
context.contentResolver.openOutputStream(destinationUri)?.use { outputStream ->
dbFile.inputStream().use { inputStream ->
copyFile(inputStream, outputStream)
}
} ?: throw IllegalArgumentException("Unable to open destination Uri for writing")
context.runOnUiThread {
Toast.makeText(context, "Database exported successfully!", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Closing application to avoid changes.", Toast.LENGTH_LONG).show()
context.finishAffinity()
}
} catch (e: Exception) {
Log.e("ScanScreenController", "Error exporting database $e")
context.runOnUiThread {
Toast.makeText(context, "Error exporting database: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
private fun importDatabase(sourceUri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) {
try {
if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) {
context.runOnUiThread {
Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show()
}
return@launch
}
application.container.db.close()
val dbPath = context.getDatabasePath(roomDbName)
val shmFile = File(dbPath.parent, "$roomDbName-shm")
val walFile = File(dbPath.parent, "$roomDbName-wal")
// Delete existing database files
if (dbPath.exists()) dbPath.delete()
if (shmFile.exists()) shmFile.delete()
if (walFile.exists()) walFile.delete()
val dbFile = File(dbPath.absolutePath)
context.contentResolver.openInputStream(sourceUri)?.use { inputStream ->
dbFile.outputStream().use { outputStream ->
copyFile(inputStream, outputStream)
}
} ?: throw IllegalArgumentException("Unable to open source Uri for reading")
context.runOnUiThread {
Toast.makeText(context, "Database imported successfully!", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Reopen the app to finish import process!", Toast.LENGTH_LONG).show()
context.finishAffinity()
}
} catch (e: Exception) {
Log.e("ScanScreenController", "Error importing database $e")
context.runOnUiThread {
Toast.makeText(context, "Error importing database: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
private fun getFileNameFromUri(uri: Uri): String? {
var fileName: String? = null
val cursor = context.contentResolver.query(uri, null, null, null, null)
cursor?.use {
if (it.moveToFirst()) {
val nameIndex = it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
fileName = it.getString(nameIndex)
}
}
return fileName
}
private fun copyFile(inputStream: InputStream, outputStream: OutputStream) {
val buffer = ByteArray(1024)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
outputStream.flush()
}
private fun importApk(uri: Uri) { private fun importApk(uri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) { context.lifecycleScope.launch(Dispatchers.IO) {
context.contentResolver.openInputStream(uri).use { context.contentResolver.openInputStream(uri).use {

View File

@ -0,0 +1,320 @@
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
)
}
}
}

View File

@ -0,0 +1,115 @@
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
}
}

View File

@ -33,11 +33,15 @@ import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap import com.github.nacabaro.vbhelper.utils.getBitmap
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun StorageDialog( fun StorageDialog(
characterId: Long, characterId: Long,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onClickDelete: () -> Unit,
onSendToBracelet: () -> Unit, onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit, onClickSetActive: () -> Unit,
onClickSendToAdventure: (time: Long) -> Unit onClickSendToAdventure: (time: Long) -> Unit
@ -93,7 +97,7 @@ fun StorageDialog(
val dpSize = (characterSprite.value!!.width * 4 / density).dp val dpSize = (characterSprite.value!!.width * 4 / density).dp
Image( Image(
bitmap = imageBitmap, bitmap = imageBitmap,
contentDescription = "Character image", contentDescription = stringResource(R.string.storage_character_image_description),
filterQuality = FilterQuality.None, filterQuality = FilterQuality.None,
modifier = Modifier modifier = Modifier
.size(dpSize) .size(dpSize)
@ -103,7 +107,7 @@ fun StorageDialog(
val nameDpSize = (characterName.value!!.width * 4 / density).dp val nameDpSize = (characterName.value!!.width * 4 / density).dp
Image( Image(
bitmap = nameImageBitmap, bitmap = nameImageBitmap,
contentDescription = "Character image", contentDescription = stringResource(R.string.storage_character_image_description),
filterQuality = FilterQuality.None, filterQuality = FilterQuality.None,
modifier = Modifier modifier = Modifier
.size(nameDpSize) .size(nameDpSize)
@ -120,7 +124,7 @@ fun StorageDialog(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
) { ) {
Text(text = "Send to watch") Text(text = stringResource(R.string.storage_send_to_watch))
} }
Spacer( Spacer(
modifier = Modifier modifier = Modifier
@ -129,7 +133,7 @@ fun StorageDialog(
Button( Button(
onClick = onClickSetActive, onClick = onClickSetActive,
) { ) {
Text(text = "Set active") Text(text = stringResource(R.string.storage_set_active))
} }
} }
Button( Button(
@ -139,14 +143,21 @@ fun StorageDialog(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text(text = "Send on adventure") Text(text = stringResource(R.string.storage_send_on_adventure))
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = onClickDelete
) {
Text(text = stringResource(R.string.storage_delete_character))
} }
Button( Button(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
onClick = onDismissRequest onClick = onDismissRequest
) { ) {
Text(text = "Close") Text(text = stringResource(R.string.storage_close))
} }
} }
} }

View File

@ -1,5 +1,6 @@
package com.github.nacabaro.vbhelper.screens.storageScreen package com.github.nacabaro.vbhelper.screens.storageScreen
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.gestures.scrollable
@ -11,14 +12,15 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -28,12 +30,12 @@ import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.launch import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
@ -42,30 +44,23 @@ fun StorageScreen(
storageScreenController: StorageScreenControllerImpl, storageScreenController: StorageScreenControllerImpl,
adventureScreenController: AdventureScreenControllerImpl adventureScreenController: AdventureScreenControllerImpl
) { ) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db) val storageRepository = StorageRepository(application.container.db)
val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) } val characterList by storageRepository.getAllCharacters().collectAsState(initial = emptyList())
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
LaunchedEffect(storageRepository, selectedCharacter) { var selectedCharacter by remember { mutableStateOf<Long?>(null) }
coroutineScope.launch {
val characterList = storageRepository.getAllCharacters()
monList.value = characterList
}
}
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "My characters", text = stringResource(R.string.storage_my_characters_title),
onAdventureClick = { onAdventureClick = {
navController.navigate(NavigationItems.Adventure.route) navController.navigate(NavigationItems.Adventure.route)
} }
) )
} }
) { contentPadding -> ) { contentPadding ->
if (monList.value.isEmpty()) { if (characterList.isEmpty()) {
Column ( Column (
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
@ -74,7 +69,7 @@ fun StorageScreen(
.fillMaxSize() .fillMaxSize()
) { ) {
Text( Text(
text = "Nothing to see here", text = stringResource(R.string.storage_nothing_to_see_here),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier modifier = Modifier
) )
@ -86,7 +81,7 @@ fun StorageScreen(
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical) .scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
items(monList.value) { index -> items(characterList) { index ->
CharacterEntry( CharacterEntry(
icon = BitmapData( icon = BitmapData(
bitmap = index.spriteIdle, bitmap = index.spriteIdle,
@ -99,7 +94,7 @@ fun StorageScreen(
} else { } else {
Toast.makeText( Toast.makeText(
application, application,
"This character is in an adventure", application.getString(R.string.storage_in_adventure_toast),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.navigate( navController.navigate(
@ -107,6 +102,16 @@ fun StorageScreen(
) )
} }
}, },
cardColors = if (index.active) {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primary
)
} else {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
)
}
) )
} }
} }
@ -138,6 +143,16 @@ fun StorageScreen(
timeInMinutes = time timeInMinutes = time
) )
selectedCharacter = null selectedCharacter = null
},
onClickDelete = {
storageScreenController
.deleteCharacter(
characterId = selectedCharacter!!,
onCompletion = {
Log.d("StorageScreen", "Character deleted")
}
)
selectedCharacter = null
} }
) )
} }

View File

@ -2,4 +2,5 @@ package com.github.nacabaro.vbhelper.screens.storageScreen
interface StorageScreenController { interface StorageScreenController {
fun setActive(characterId: Long, onCompletion: () -> Unit) fun setActive(characterId: Long, onCompletion: () -> Unit)
fun deleteCharacter(characterId: Long, onCompletion: () -> Unit)
} }

View File

@ -28,4 +28,21 @@ class StorageScreenControllerImpl(
} }
} }
} }
override fun deleteCharacter(characterId: Long, onCompletion: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.userCharacterDao()
.deleteCharacterById(characterId)
componentActivity.runOnUiThread {
Toast.makeText(
componentActivity,
"Character deleted!",
Toast.LENGTH_SHORT
).show()
onCompletion()
}
}
}
} }

View File

@ -0,0 +1,15 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
class CardRepository (
private val db: AppDatabase
) {
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon> {
return db
.cardDao()
.getCardIconByCharaId(charaId = charaId)
}
}

View File

@ -0,0 +1,28 @@
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
}
}
}

View File

@ -3,15 +3,24 @@ package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.dtos.CardDtos import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
class DexRepository ( class DexRepository (
private val db: AppDatabase private val db: AppDatabase
) { ) {
suspend fun getAllDims(): List<CardDtos.CardProgress> { fun getAllDims(): Flow<List<CardDtos.CardProgress>> {
return db.dexDao().getCardsWithProgress() return db.dexDao().getCardsWithProgress()
} }
suspend fun getCharactersByDimId(cardId: Long): List<CharacterDtos.CardProgress> { fun getCharactersByCardId(cardId: Long): Flow<List<CharacterDtos.CardCharaProgress>> {
return db.dexDao().getSingleCardProgress(cardId) 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)
}
} }

View File

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

View File

@ -0,0 +1,13 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.card.Card
import kotlinx.coroutines.flow.Flow
class ScanRepository(
val database: AppDatabase
) {
fun getCardDetails(characterId: Long): Flow<Card> {
return database.cardDao().getCardByCharacterId(characterId)
}
}

View File

@ -6,11 +6,12 @@ import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
class StorageRepository ( class StorageRepository (
private val db: AppDatabase private val db: AppDatabase
) { ) {
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites> { fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>> {
return db.userCharacterDao().getAllCharacters() return db.userCharacterDao().getAllCharacters()
} }
@ -18,19 +19,19 @@ class StorageRepository (
return db.userCharacterDao().getCharacterWithSprites(id) return db.userCharacterDao().getCharacterWithSprites(id)
} }
suspend fun getCharacterBeData(id: Long): BECharacterData { fun getCharacterBeData(id: Long): Flow<BECharacterData> {
return db.userCharacterDao().getBeData(id) return db.userCharacterDao().getBeData(id)
} }
suspend fun getTransformationHistory(characterId: Long): List<CharacterDtos.TransformationHistory>? { fun getTransformationHistory(characterId: Long): Flow<List<CharacterDtos.TransformationHistory>> {
return db.userCharacterDao().getTransformationHistory(characterId) return db.userCharacterDao().getTransformationHistory(characterId)
} }
suspend fun getCharacterVbData(id: Long): VBCharacterData { fun getCharacterVbData(id: Long): Flow<VBCharacterData> {
return db.userCharacterDao().getVbData(id) return db.userCharacterDao().getVbData(id)
} }
suspend fun getSpecialMissions(id: Long): List<SpecialMissions> { fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>> {
return db.userCharacterDao().getSpecialMissions(id) return db.userCharacterDao().getSpecialMissions(id)
} }
@ -38,7 +39,7 @@ class StorageRepository (
return db.itemDao().getItem(id) return db.itemDao().getItem(id)
} }
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? { fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?> {
return db.userCharacterDao().getActiveCharacter() return db.userCharacterDao().getActiveCharacter()
} }
@ -46,7 +47,7 @@ class StorageRepository (
return db.userCharacterDao().deleteCharacterById(id) return db.userCharacterDao().deleteCharacterById(id)
} }
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> { fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>> {
return db.adventureDao().getAdventureCharacters() return db.adventureDao().getAdventureCharacters()
} }

View File

@ -1,6 +1,11 @@
package com.github.nacabaro.vbhelper.utils package com.github.nacabaro.vbhelper.utils
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.github.cfogrady.vb.dim.sprite.SpriteData import com.github.cfogrady.vb.dim.sprite.SpriteData
// simple, but smooth // simple, but smooth
@ -10,6 +15,32 @@ data class BitmapData (
val height: Int val height: Int
) )
data class ImageBitmapData(
val imageBitmap: ImageBitmap,
val dpWidth: Dp,
val dpHeight: Dp
)
fun BitmapData.getImageBitmap(
context: Context,
multiplier: Int,
obscure: Boolean
): ImageBitmapData {
val density: Float = context.resources.displayMetrics.density
val imageBitmap: ImageBitmap = if (obscure) {
this.getObscuredBitmap().asImageBitmap()
} else {
this.getBitmap().asImageBitmap()
}
return ImageBitmapData(
imageBitmap = imageBitmap,
dpWidth = (this.width * multiplier / density).dp,
dpHeight = (this.height * multiplier / density).dp
)
}
fun BitmapData.getBitmap(): Bitmap { fun BitmapData.getBitmap(): Bitmap {
return Bitmap.createBitmap(createARGBIntArray(), this.width, this.height, Bitmap.Config.HARDWARE) return Bitmap.createBitmap(createARGBIntArray(), this.width, this.height, Bitmap.Config.HARDWARE)
} }

View File

@ -0,0 +1,220 @@
<resources>
<string name="app_name">VBHelper</string>
<string name="beta_warning_message_main">
Este aplicativo ainda está em versão alfa e não está completo. Não o use para armazenar personagens importantes para você, pois futuras atualizações podem apagar todos os seus personagens. Desculpe pelo transtorno!
</string>
<string name="beta_warning_message_compatibility">
O aplicativo agora deve funcionar com o VB original e com o VH.
</string>
<string name="beta_warning_message_thanks">
Obrigado pela compreensão e pela paciência. Atenciosamente, a equipe de desenvolvimento.
</string>
<string name="beta_warning_button_dismiss">Fechar</string>
<string name="nav_scan">Escanear</string>
<string name="nav_battle">Batalhas</string>
<string name="nav_home">Início</string>
<string name="nav_dex">Dex</string>
<string name="nav_card_adventure">Aventura do card</string>
<string name="nav_storage">Armazenamento</string>
<string name="nav_settings">Configurações</string>
<string name="nav_viewer">Visualizador</string>
<string name="nav_card">Card</string>
<string name="nav_items">Itens</string>
<string name="nav_my_items">Meus itens</string>
<string name="nav_items_store">Loja de itens</string>
<string name="nav_apply_item">Aplicar item</string>
<string name="nav_adventure">Aventura</string>
<string name="nav_credits">Créditos</string>
<string name="adventure_title">Aventura</string>
<string name="adventure_empty_state">Nada para ver aqui</string>
<string name="home_title">VB Helper</string>
<string name="home_adventure_mission_finished">
Um dos seus personagens terminou a missão de aventura!
</string>
<string name="scan_secrets_not_initialized">
Os segredos ainda não foram inicializados. Tente novamente.
</string>
<string name="scan_secrets_not_imported">
Os segredos ainda não foram importados. Vá em Configurações e importe o APK.
</string>
<string name="scan_title">Escanear um Vital Bracelet</string>
<string name="scan_vb_to_app">Vital Bracelet para o app</string>
<string name="scan_app_to_vb">App para o Vital Bracelet</string>
<string name="scan_no_nfc_on_device">O dispositivo não possui NFC!</string>
<string name="scan_tag_not_vb">A tag detectada não é do Vital Bracelet.</string>
<string name="scan_missing_secrets">
Segredos ausentes. Vá em Configurações e importe o APK do Vital Arena.
</string>
<string name="scan_sent_character_success">Personagem enviado com sucesso!</string>
<string name="scan_error_generic">Ops!</string>
<string name="scan_sent_dim_success">DIM enviado com sucesso!</string>
<string name="scan_nfc_must_be_enabled">O NFC precisa estar ativado.</string>
<string name="settings_title">Configurações</string>
<string name="settings_section_nfc">Comunicação NFC</string>
<string name="settings_section_dim_bem">Gerenciamento de DiM/BEm</string>
<string name="settings_section_about">Sobre e créditos</string>
<string name="settings_section_data">Gerenciamento de dados</string>
<string name="settings_import_apk_title">Importar APK</string>
<string name="settings_import_apk_desc">
Importar segredos do APK do Vital Arena 2.1.0
</string>
<string name="settings_import_card_title">Importar card</string>
<string name="settings_import_card_desc">
Importar arquivo de card DiM/BEm
</string>
<string name="settings_credits_title">Créditos</string>
<string name="settings_credits_desc">Créditos</string>
<string name="settings_about_title">Sobre</string>
<string name="settings_about_desc">Sobre</string>
<string name="settings_export_data_title">Exportar dados</string>
<string name="settings_export_data_desc">
Exportar banco de dados do aplicativo
</string>
<string name="settings_import_data_title">Importar dados</string>
<string name="settings_import_data_desc">
Importar banco de dados do aplicativo
</string>
<string name="credits_title">Créditos</string>
<string name="credits_section_reverse_engineering">Engenharia reversa</string>
<string name="credits_section_app_development">Desenvolvimento do aplicativo</string>
<string name="credits_cyanic_description">
Reverteu o firmware e nos ajudou durante o desenvolvimento.
</string>
<string name="credits_cfogrady_description">
Desenvolveu vb-lib-nfc e parte deste aplicativo.
</string>
<string name="credits_nacabaro_description">
Desenvolveu este aplicativo.
</string>
<string name="credits_lightheel_description">
Está desenvolvendo a parte de batalhas deste aplicativo, incluindo o servidor. Ainda em desenvolvimento.
</string>
<string name="credits_shvstrz_description">
Responsável pelo design do ícone do aplicativo em SVG.
</string>
<string name="action_place_near_reader">Aproxime o seu Vital Bracelet do leitor...</string>
<string name="action_cancel">Cancelar</string>
<string name="read_character_title">Ler personagem</string>
<string name="read_character_prepare_device">Prepare seu dispositivo!</string>
<string name="read_character_go_to_connect">Vá em conectar e, quando estiver pronto, pressione confirmar!</string>
<string name="read_character_confirm">Confirmar</string>
<string name="reading_character_title">Lendo personagem</string>
<string name="write_card_title">Escrevendo detalhes do cartão</string>
<string name="write_card_icon_description">Ícone do cartão</string>
<string name="write_card_device_ready">Prepare o seu dispositivo!</string>
<string name="write_card_required_card">Você vai precisar do cartão %1$s!</string>
<string name="write_card_confirm">Confirmar</string>
<string name="write_character_title">Escrevendo personagem</string>
<string name="write_character_icon_description">Ícone do personagem</string>
<string name="write_character_success">
Cartão instalado com sucesso!!
</string>
<string name="write_character_wait_ready">
Aguarde até que seu dispositivo esteja pronto e então toque em Confirmar
</string>
<string name="write_character_confirm">Confirmar</string>
<string name="sending_card_title">Enviando cartão</string>
<string name="writing_character_action_title">Escrevendo personagem</string>
<string name="items_title">Itens</string>
<string name="items_no_items">Nenhum item</string>
<string name="items_store_credits">%1$d créditos</string>
<string name="items_not_enough_credits">Créditos insuficientes</string>
<string name="items_purchase_success">Compra realizada com sucesso!</string>
<string name="obtained_item_you_have">Você obteve %1$d deste item</string>
<string name="obtained_item_you_also_got_credits">Você também recebeu %1$d créditos</string>
<string name="obtained_item_dismiss">Fechar</string>
<string name="choose_character_title">Escolher personagem</string>
<string name="choose_character_item_applied">Item aplicado!</string>
<string name="item_dialog_costs_credits">Custa %1$d créditos</string>
<string name="item_dialog_you_have_quantity">Você tem %1$d deste item</string>
<string name="item_dialog_use">Usar item</string>
<string name="item_dialog_purchase">Comprar</string>
<string name="item_dialog_cancel">Cancelar</string>
<string name="battles_online_title">Batalhas online</string>
<string name="battles_coming_soon">Em breve</string>
<string name="cards_my_cards_title">Meus cards</string>
<string name="card_view_discovered_characters">Personagens descobertos</string>
<string name="dex_chara_icon_description">Ícone do personagem</string>
<string name="dex_chara_name_icon_description">Nome do personagem</string>
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute">Stg: %1$s, Atr: %2$s</string>
<string name="dex_chara_unknown_name">\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</string>
<string name="dex_chara_stats_unknown">HP: -, BP: -, AP: -</string>
<string name="dex_chara_requirements">
Tr: %1$d; Bt: %2$d; Vr: %3$d; Wr: %4$d%%; Ct: %5$dh
</string>
<string name="dex_chara_adventure_level">AdvLvl %1$d</string>
<string name="dex_chara_fusions_button">Fusões</string>
<string name="dex_chara_close_button">Fechar</string>
<string name="card_adventure_missions_title">Missões de aventura</string>
<string name="card_entry_characters_obtained">%1$d de %2$d personagens obtidos</string>
<string name="card_entry_edit">Editar card</string>
<string name="card_entry_delete">Excluir card</string>
<string name="storage_my_characters_title">Meus personagens</string>
<string name="storage_nothing_to_see_here">Nada para ver aqui</string>
<string name="storage_in_adventure_toast">Este personagem está em uma aventura</string>
<string name="storage_character_image_description">Imagem do personagem</string>
<string name="storage_send_to_watch">Enviar para o relógio</string>
<string name="storage_set_active">Definir como ativo</string>
<string name="storage_send_on_adventure">Enviar para aventura</string>
<string name="storage_delete_character">Excluir personagem</string>
<string name="storage_close">Fechar</string>
<string name="home_vbdim_vitals">Vitais</string>
<string name="home_vbdim_trophies">Troféus</string>
<string name="home_vbdim_mood">Humor</string>
<string name="home_vbdim_next_timer">Próximo timer</string>
<string name="home_vbdim_total_battle_win">% vitórias totais</string>
<string name="home_vbdim_current_phase_win">% vitórias fase atual</string>
<string name="home_vbdim_special_missions">Missões especiais</string>
<string name="special_mission_none">Nenhuma missão selecionada</string>
<string name="special_mission_steps">Ande %1$d passos</string>
<string name="special_mission_battles">Lute %1$d vezes</string>
<string name="special_mission_wins">Vença %1$d batalhas</string>
<string name="special_mission_vitals">Ganhe %1$d vitals</string>
<string name="special_mission_steps_progress">Andou %1$d passos</string>
<string name="special_mission_battles_progress">Lutou %1$d vezes</string>
<string name="special_mission_wins_progress">Venceu %1$d batalhas</string>
<string name="special_mission_vitals_progress">Ganhou %1$d vitals</string>
<string name="special_mission_icon_content_description">Ícone de missão especial</string>
</resources>

View File

@ -1,3 +1,229 @@
<resources> <resources>
<string name="app_name">VBHelper</string> <string name="app_name">VBHelper</string>
<string name="beta_warning_message_main">
This application is currently in alpha and it is not complete. Do not use it to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!
</string>
<string name="beta_warning_message_compatibility">
The application should now work with the original VB and the VH.
</string>
<string name="beta_warning_message_thanks">
Thank you for your understanding and patience. Sincerely, the dev team.
</string>
<string name="beta_warning_button_dismiss">
Dismiss
</string>
<string name="nav_scan">Scan</string>
<string name="nav_battle">Battle</string>
<string name="nav_home">Home</string>
<string name="nav_dex">Dex</string>
<string name="nav_card_adventure">Card adventure</string>
<string name="nav_storage">Storage</string>
<string name="nav_settings">Settings</string>
<string name="nav_viewer">Viewer</string>
<string name="nav_card">Card</string>
<string name="nav_items">Items</string>
<string name="nav_my_items">My items</string>
<string name="nav_items_store">Items store</string>
<string name="nav_apply_item">Apply item</string>
<string name="nav_adventure">Adventure</string>
<string name="nav_credits">Credits</string>
<string name="adventure_title">Adventure</string>
<string name="adventure_empty_state">Nothing to see here</string>
<string name="home_title">VB Helper</string>
<string name="home_adventure_mission_finished">
One of your characters has finished their adventure mission!
</string>
<string name="scan_secrets_not_initialized">
Secrets is not yet initialized. Try again.
</string>
<string name="scan_secrets_not_imported">
Secrets not yet imported. Go to Settings and Import APK.
</string>
<string name="scan_title">Scan a Vital Bracelet</string>
<string name="scan_vb_to_app">Vital Bracelet to App</string>
<string name="scan_app_to_vb">App to Vital Bracelet</string>
<string name="scan_no_nfc_on_device">No NFC on device!</string>
<string name="scan_tag_not_vb">Tag detected is not VB</string>
<string name="scan_missing_secrets">
Missing Secrets. Go to settings and import Vital Arena APK.
</string>
<string name="scan_sent_character_success">Sent character successfully!</string>
<string name="scan_error_generic">Whoops</string>
<string name="scan_sent_dim_success">Sent DIM successfully!</string>
<string name="scan_nfc_must_be_enabled">NFC must be enabled</string>
<string name="settings_title">Settings</string>
<string name="settings_section_nfc">NFC Communication</string>
<string name="settings_section_dim_bem">DiM/BEm management</string>
<string name="settings_section_about">About and credits</string>
<string name="settings_section_data">Data management</string>
<string name="settings_import_apk_title">Import APK</string>
<string name="settings_import_apk_desc">
Import Secrets From Vital Arena 2.1.0 APK
</string>
<string name="settings_import_card_title">Import card</string>
<string name="settings_import_card_desc">
Import DiM/BEm card file
</string>
<string name="settings_credits_title">Credits</string>
<string name="settings_credits_desc">Credits</string>
<string name="settings_about_title">About</string>
<string name="settings_about_desc">About</string>
<string name="settings_export_data_title">Export data</string>
<string name="settings_export_data_desc">
Export application database
</string>
<string name="settings_import_data_title">Import data</string>
<string name="settings_import_data_desc">
Import application database
</string>
<string name="credits_title">Credits</string>
<string name="credits_section_reverse_engineering">Reverse engineering</string>
<string name="credits_section_app_development">Application development</string>
<string name="credits_cyanic_description">
Reversed the firmware and helped us during development.
</string>
<string name="credits_cfogrady_description">
Developed vb-lib-nfc and part of this application.
</string>
<string name="credits_nacabaro_description">
Developed this application.
</string>
<string name="credits_lightheel_description">
Developing the battling part for this application, including server. Still in the works.
</string>
<string name="credits_shvstrz_description">
Designing the app icon in SVG.
</string>
<string name="action_place_near_reader">Place your Vital Bracelet near the reader...</string>
<string name="action_cancel">Cancel</string>
<string name="read_character_title">Read character</string>
<string name="read_character_prepare_device">Prepare your device!</string>
<string name="read_character_go_to_connect">Go to connect and when ready press confirm!</string>
<string name="read_character_confirm">Confirm</string>
<string name="reading_character_title">Reading character</string>
<string name="write_card_title">Writing card details</string>
<string name="write_card_icon_description">Card icon</string>
<string name="write_card_device_ready">Get your device ready!</string>
<string name="write_card_required_card">You will need %1$s card!</string>
<string name="write_card_confirm">Confirm</string>
<string name="write_character_title">Writing character</string>
<string name="write_character_icon_description">Character icon</string>
<string name="write_character_success">
Card installed successfully!!
</string>
<string name="write_character_wait_ready">
Wait until your device is ready, then tap Confirm
</string>
<string name="write_character_confirm">Confirm</string>
<string name="sending_card_title">Sending card</string>
<string name="writing_character_action_title">Writing character</string>
<string name="items_title">Items</string>
<string name="items_no_items">No items</string>
<string name="items_store_credits">%1$d credits</string>
<string name="items_not_enough_credits">Not enough credits</string>
<string name="items_purchase_success">Purchase successful!</string>
<string name="obtained_item_you_have">You have obtained %1$d of this item</string>
<string name="obtained_item_you_also_got_credits">You also got %1$d credits</string>
<string name="obtained_item_dismiss">Dismiss</string>
<string name="choose_character_title">Choose character</string>
<string name="choose_character_item_applied">Item applied!</string>
<string name="item_dialog_costs_credits">Costs %1$d credits</string>
<string name="item_dialog_you_have_quantity">You have %1$d of this item</string>
<string name="item_dialog_use">Use item</string>
<string name="item_dialog_purchase">Purchase</string>
<string name="item_dialog_cancel">Cancel</string>
<string name="battles_online_title">Online battles</string>
<string name="battles_coming_soon">Coming soon</string>
<string name="cards_my_cards_title">My cards</string>
<string name="card_view_discovered_characters">Discovered characters</string>
<string name="dex_chara_icon_description">Character icon</string>
<string name="dex_chara_name_icon_description">Character name</string>
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute">Stg: %1$s, Atr: %2$s</string>
<string name="dex_chara_unknown_name">\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</string>
<string name="dex_chara_stats_unknown">HP: -, BP: -, AP: -</string>
<string name="dex_chara_requirements">
Tr: %1$d; Bt: %2$d; Vr: %3$d; Wr: %4$d%%; Ct: %5$dh
</string>
<string name="dex_chara_adventure_level">AdvLvl %1$d</string>
<string name="dex_chara_fusions_button">Fusions</string>
<string name="dex_chara_close_button">Close</string>
<string name="card_adventure_missions_title">Adventure missions</string>
<string name="card_entry_characters_obtained">%1$d of %2$d characters obtained</string>
<string name="card_entry_edit">Edit card</string>
<string name="card_entry_delete">Delete card</string>
<string name="storage_my_characters_title">My characters</string>
<string name="storage_nothing_to_see_here">Nothing to see here</string>
<string name="storage_in_adventure_toast">This character is in an adventure</string>
<string name="storage_character_image_description">Character image</string>
<string name="storage_send_to_watch">Send to watch</string>
<string name="storage_set_active">Set active</string>
<string name="storage_send_on_adventure">Send on adventure</string>
<string name="storage_delete_character">Delete character</string>
<string name="storage_close">Close</string>
<string name="home_vbdim_vitals">Vitals</string>
<string name="home_vbdim_trophies">Trophies</string>
<string name="home_vbdim_mood">Mood</string>
<string name="home_vbdim_next_timer">Next timer</string>
<string name="home_vbdim_total_battle_win">Total battle win %</string>
<string name="home_vbdim_current_phase_win">Current phase win %</string>
<string name="home_vbdim_special_missions">Special missions</string>
<string name="special_mission_none">No mission selected</string>
<string name="special_mission_steps">Walk %1$d steps</string>
<string name="special_mission_battles">Battle %1$d times</string>
<string name="special_mission_wins">Win %1$d battles</string>
<string name="special_mission_vitals">Earn %1$d vitals</string>
<string name="special_mission_steps_progress">Walked %1$d steps</string>
<string name="special_mission_battles_progress">Battled %1$d times</string>
<string name="special_mission_wins_progress">Won %1$d battles</string>
<string name="special_mission_vitals_progress">Earned %1$d vitals</string>
<string name="special_mission_icon_content_description">Special mission icon</string>
<string name="home_special_mission_delete_main">Are you sure you want to delete this special mission with this progress?</string>
<string name="home_special_mission_delete_dismiss">Dismiss</string>
<string name="home_special_mission_delete_remove">Remove</string>
</resources> </resources>

View File

@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.7.3" agp = "8.13.2"
datastore = "1.1.2" datastore = "1.1.2"
kotlin = "2.0.0" kotlin = "2.0.0"
coreKtx = "1.15.0" coreKtx = "1.15.0"

View File

@ -1,6 +1,6 @@
#Tue Dec 24 10:55:57 UTC 2024 #Tue Dec 24 10:55:57 UTC 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists