Compare commits

...
33 Commits
Author SHA1 Message Date
Nacho f96f00e05a Version up
And change the original pop up to mention this works with VB, my bad
2025-08-06 01:22:43 +02:00
nacabaroandGitHub 3c17e93d0f Merge pull request #35 from nacabaro/vb/special_missions
Special missions
2025-08-06 01:21:00 +02:00
Nacho f8ce81e932 Special missions
- Basic implementation is complete
- Added 8 different special missions, 4 are easy and more expensive, and 4 are cheaper and more difficult
- Added 9 missions of each so people can test themselves
- Also added checks to disallow BE digimon to have special missions
- UI elements to display the status of the missions
- Finishing a mission awards a random price (TODO: Make the price be based on the mission difficulty)
2025-08-06 01:19:19 +02:00
Nacho 7bb7693876 Version Up 2025-08-04 19:57:39 +02:00
nacabaroandGitHub 2b33042fe0 Merge pull request #34 from nacabaro/vb/nfc_compat
Added basic VB support
2025-08-04 19:56:12 +02:00
Nacho 4d8dcde26e Forgot to add all the UI methods to preview VB characters 2025-08-04 13:15:07 +02:00
Nacho 0b1eed7da9 Increase version 2025-07-29 23:08:45 +02:00
Nacho 36c3e25fd3 Last minute bug related to sending the active character from the main screen. 2025-07-29 19:22:23 +02:00
nacabaroandGitHub ac05dfc541 Merge pull request #33 from nacabaro/vb/nfc_compat
Add VB Compatibility
2025-07-29 18:42:10 +02:00
Nacho 220a61a553 Bug fixes
- Fixed an issue with the calendar, now I store the date as year/month/day in the database directly.
- VB expects to have a year between 2021 and 2032, for some reason it gives me back a 2000 year when reading an empty history. I just patched it in the application to return as year 0 in those cases.
- VitalsHistory works as intended now. Also verified functionality of the new classes.
- Moved a few SQL queries too, since they didn't belong where they were.

Also added a small feature, now the application knows if you entered the scan screen through a character in the storage, or the home screen. If there is an active character and you opened the home screen, the application will let you send the active character, while if it was opened through the storage screen, the application will send the character from the storage screen.

What's now missing is to create a VBSCanScreen, and depending on which device we are using, make use of one screen or the other.
2025-07-29 10:36:07 +02:00
Nacho d847f600f1 Quite a few things
- Renamed a few objects to avoid legality issues
- Added a few accompanying tables to support the VB/VH
  - While we speak about tables, added an auxiliary table to track card adventure progress
- Extracted NFC character generation and extraction to different classes, should result in a cleaner ScanScreenController
2025-07-29 02:50:15 +02:00
Nacho f7a4039d66 Merge branch 'main' into vb/nfc_compat
# Conflicts:
#	app/src/main/java/com/github/nacabaro/vbhelper/screens/scanScreen/ScanScreenControllerImpl.kt
2025-07-29 01:35:21 +02:00
nacabaroandGitHub 1eed6a4c17 Merge pull request #32 from nacabaro/navigation/animations
Graphical things
2025-07-29 01:29:55 +02:00
nacabaroandGitHub 1a09e40d1d Merge pull request #31 from nacabaro/database/get_all_sprites
Fetch all the character sprites into the internal database
2025-07-29 01:26:49 +02:00
Nacho 13051145b7 Few things:
- Speed up the fade animation a tad bit
- Added a back arrow in the scan screen
- Improved a bit the placeholder of the battles screen, since it'd throw out the transition animation.
2025-07-29 00:01:02 +02:00
Nacho 1a150ce7ce Few things:
- Speed up the fade animation a tad bit
- Added a back arrow in the scan screen
- Improved a bit the placeholder of the battles screen, since it'd throw out the transition animation.
2025-07-28 23:58:41 +02:00
Nacho 39ba48d867 Forgot to remove the dependency to SpriteRepo 2025-07-27 19:46:07 +02:00
Nacho 3f324ca141 SpriteRepo is not needed anymore 2025-07-27 19:44:12 +02:00
Nacho 1e28a8c249 Application now imports all the sprites related to the character into the internal database. 2025-07-27 19:42:25 +02:00
Nacho d46769b0cb Added credits for the app in preparation for version 0.1 2025-07-26 22:26:41 +02:00
Nacho fce05870c5 This should allow the app to build, otherwise it won't or it will crash 2025-07-26 14:23:53 +02:00
Nacho 9f5f806786 Added basic stuff for compatibility with VB 2025-06-01 18:51:30 +02:00
nacabaroandGitHub d749af0a9c Merge pull request #27 from cfogrady/FixStageAndAttributeFromCharacter
Fix stage and attribute coming from character instead of UserCharacter
2025-04-08 15:35:13 +02:00
Christopher O'Grady 076f6bfacf Fix stage and attribute coming from character instead of UserCharacter 2025-04-05 20:31:03 -04:00
nacabaroandGitHub a4f3b701e5 Merge pull request #26 from cfogrady/RemoveDuplicatedData
Remove duplicated stage and attribute from UserCharacter
2025-03-22 00:34:07 +01:00
Christopher O'Grady da134247b1 Remove duplicated stage and attribute from UserCharacter
These fields are on the Character entity.
2025-03-19 15:33:52 -04:00
Nacho f7b3b7256a VB NFC compatibility
- Refactored some names (not really relevant)
- Added the ability to store special missions inside the application's database
- Refactored the conversion code into two classes inside the scan screen package
- Added the missing tables to store the necessary vb data

Also not relevant to this update
- Updated adventure progress app wide, so that instead of it being stored in a character basis, it is shared across all characters in the same dim
2025-02-10 17:44:13 +01:00
nacabaroandGitHub e36a700d9f Merge pull request #25 from nacabaro/database/adventure
Adventure mode
2025-01-26 02:01:24 +01:00
Nacho fb6b6d2825 While I'm at it...
- Resized some buttons in the dialogs
- Added a dismiss button in get item dialog after adventure and in the send on adventure choose time dialogs
- Moved the export/import data lower in the settings screen
2025-01-26 01:59:43 +01:00
Nacho e17f6c23e4 Renaming terms and other bits and bobs
- Changed adventure database so that the original intended time is also stored with the final time. This will come useful once the algorithm for determining which object to give is made.
2025-01-26 01:40:32 +01:00
Nacho 409474b5d1 Phew
- Added adventure screen, dialogs, components and controllers needed to rock.
- Modified DTOs so that it is possible to tell if a character is in adventure or not
- Updated layout of items database
- Fixed an issue with importing bems where the character name would be read incorrectly
- Also added a ton of checks
2025-01-26 01:25:25 +01:00
nacabaroandGitHub 547318864b Merge pull request #24 from nacabaro/cleanup
Bigger icons plus material obscurity
2025-01-21 13:35:43 +01:00
nacabaroandGitHub 1a38cefb92 Merge pull request #23 from nacabaro/cleanup
Cleanup MainActivity
2025-01-21 13:12:38 +01:00
77 changed files with 3090 additions and 757 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ android {
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0"
versionName = "Alpha 0.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Binary file not shown.
@@ -9,9 +9,13 @@ import androidx.compose.runtime.Composable
import com.github.nacabaro.vbhelper.navigation.AppNavigation
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@@ -39,6 +43,10 @@ class MainActivity : ComponentActivity() {
)
val settingsScreenController = SettingsScreenControllerImpl(this)
val itemsScreenController = ItemsScreenControllerImpl(this)
val adventureScreenController = AdventureScreenControllerImpl(this)
val storageScreenController = StorageScreenControllerImpl(this)
val homeScreenController = HomeScreenControllerImpl(this)
val spriteViewerController = SpriteViewerControllerImpl(this)
super.onCreate(savedInstanceState)
@@ -49,7 +57,11 @@ class MainActivity : ComponentActivity() {
MainApplication(
scanScreenController = scanScreenController,
settingsScreenController = settingsScreenController,
itemsScreenController = itemsScreenController
itemsScreenController = itemsScreenController,
adventureScreenController = adventureScreenController,
homeScreenController = homeScreenController,
storageScreenController = storageScreenController,
spriteViewerController = spriteViewerController
)
}
}
@@ -77,13 +89,21 @@ class MainActivity : ComponentActivity() {
private fun MainApplication(
scanScreenController: ScanScreenControllerImpl,
settingsScreenController: SettingsScreenControllerImpl,
itemsScreenController: ItemsScreenControllerImpl
) {
itemsScreenController: ItemsScreenControllerImpl,
adventureScreenController: AdventureScreenControllerImpl,
storageScreenController: StorageScreenControllerImpl,
homeScreenController: HomeScreenControllerImpl,
spriteViewerController: SpriteViewerControllerImpl
) {
AppNavigation(
applicationNavigationHandlers = AppNavigationHandlers(
settingsScreenController,
scanScreenController,
itemsScreenController
itemsScreenController,
adventureScreenController,
storageScreenController,
homeScreenController,
spriteViewerController
)
)
}
@@ -5,11 +5,14 @@ import androidx.compose.foundation.Image
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.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -28,6 +31,9 @@ import com.github.nacabaro.vbhelper.utils.getBitmap
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.utils.getObscuredBitmap
@Composable
@@ -35,6 +41,7 @@ fun CharacterEntry(
icon: BitmapData,
modifier: Modifier = Modifier,
obscure: Boolean = false,
disabled: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 4,
onClick: () -> Unit = { }
@@ -48,7 +55,10 @@ fun CharacterEntry(
Card(
shape = shape,
onClick = onClick,
onClick = when (disabled) {
true -> { {} }
false -> onClick
},
modifier = modifier
.aspectRatio(1f)
.padding(8.dp)
@@ -110,4 +120,90 @@ fun ItemDisplay(
)
}
}
}
@Composable
fun SpecialMissionsEntry(
specialMission: SpecialMissions,
modifier: Modifier = Modifier,
onClickCard: () -> Unit = { },
) {
val textValue = when (specialMission.missionType) {
SpecialMission.Type.NONE -> "No mission selected"
SpecialMission.Type.STEPS -> "Walk ${specialMission.goal} steps"
SpecialMission.Type.BATTLES -> "Battle ${specialMission.goal} times"
SpecialMission.Type.WINS -> "Win ${specialMission.goal} battles"
SpecialMission.Type.VITALS -> "Earn ${specialMission.goal} vitals"
}
val progress = if (specialMission.status == SpecialMission.Status.COMPLETED) {
specialMission.goal
} else {
specialMission.progress
}
val completion = when (specialMission.missionType) {
SpecialMission.Type.NONE -> ""
SpecialMission.Type.STEPS -> "Walked $progress steps"
SpecialMission.Type.BATTLES -> "Battled $progress times"
SpecialMission.Type.WINS -> "Won $progress battles"
SpecialMission.Type.VITALS -> "Earned $progress vitals"
}
val icon = when (specialMission.missionType) {
SpecialMission.Type.NONE -> R.drawable.baseline_free_24
SpecialMission.Type.STEPS -> R.drawable.baseline_agility_24
SpecialMission.Type.BATTLES -> R.drawable.baseline_swords_24
SpecialMission.Type.WINS -> R.drawable.baseline_trophy_24
SpecialMission.Type.VITALS -> R.drawable.baseline_vitals_24
}
val color = when (specialMission.status)
{
SpecialMission.Status.IN_PROGRESS -> MaterialTheme.colorScheme.secondary
SpecialMission.Status.COMPLETED -> MaterialTheme.colorScheme.primary
SpecialMission.Status.FAILED -> MaterialTheme.colorScheme.error
else -> MaterialTheme.colorScheme.surfaceContainerHighest
}
Card(
modifier = modifier,
shape = androidx.compose.material.MaterialTheme.shapes.small,
onClick = if (specialMission.status == SpecialMission.Status.COMPLETED) {
onClickCard
} else {
{ }
},
colors = CardDefaults.cardColors(
containerColor = color
)
) {
Row (
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Icon(
painter = painterResource(icon),
contentDescription = "Vitals",
modifier = Modifier
.fillMaxHeight()
.padding(16.dp)
)
Column {
Text(
text = textValue,
fontFamily = MaterialTheme.typography.titleLarge.fontFamily,
fontWeight = FontWeight.Bold,
)
Text(
text = completion,
fontFamily = MaterialTheme.typography.titleSmall.fontFamily,
)
}
}
}
}
@@ -23,7 +23,8 @@ fun TopBanner(
modifier: Modifier = Modifier,
onGearClick: (() -> Unit)? = null,
onBackClick: (() -> Unit)? = null,
onScanClick: (() -> Unit)? = null
onScanClick: (() -> Unit)? = null,
onAdventureClick: (() -> Unit)? = null
) {
Box( // Use Box to overlay elements
modifier = modifier
@@ -49,7 +50,18 @@ fun TopBanner(
contentDescription = "Settings"
)
}
}
} else if (onAdventureClick != null) {
IconButton(
onClick = onAdventureClick,
modifier = Modifier
.align(Alignment.CenterEnd) // Place gear icon at the end
) {
Icon(
painter = painterResource(R.drawable.baseline_fort_24), // Use a gear icon
contentDescription = "Adventure"
)
}
}
if (onScanClick != null) {
IconButton(
@@ -0,0 +1,45 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@Dao
interface AdventureDao {
@Query("""
INSERT INTO Adventure (characterId, originalDuration, finishesAdventure)
VALUES (:characterId, :originalDuration, strftime('%s', 'now') + :timeInSeconds)
""")
fun insertNewAdventure(characterId: Long, originalDuration: Long, timeInSeconds: Long)
@Query("""
SELECT COUNT(*) FROM Adventure
""")
fun getAdventureCount(): Int
@Query("""
SELECT
uc.*,
c.stage,
c.attribute,
s.spriteIdle1 AS spriteIdle,
s.width AS spriteWidth,
s.height AS spriteHeight,
d.isBEm as isBemCard,
a.finishesAdventure AS finishesAdventure,
a.originalDuration AS originalTimeInMinutes
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Sprite s ON s.id = c.spriteId
JOIN Card d ON c.dimId = d.id
JOIN Adventure a ON uc.id = a.characterId
""")
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites>
@Query("""
DELETE FROM Adventure
WHERE characterId = :characterId
""")
suspend fun deleteAdventure(characterId: Long)
}
@@ -4,16 +4,13 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.domain.card.Card
@Dao
interface DiMDao {
interface CardDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertNewDim(card: Card): Long
@Query("SELECT * FROM Card")
suspend fun getAllDims(): List<Card>
@Query("SELECT * FROM Card WHERE dimId = :id")
@Query("SELECT * FROM Card WHERE cardId = :id")
fun getDimById(id: Int): Card?
}
@@ -0,0 +1,17 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.card.CardProgress
@Dao
interface CardProgressDao {
@Upsert
fun updateDimProgress(vararg cardProgresses: CardProgress)
@Query(
"SELECT currentStage FROM CardProgress WHERE cardId = :cardId"
)
fun getCardProgress(cardId: Int): Int
}
@@ -4,7 +4,7 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@Dao
@@ -12,40 +12,24 @@ interface CharacterDao {
@Insert
suspend fun insertCharacter(vararg characterData: Character)
@Query("SELECT * FROM Character")
suspend fun getAllCharacters(): List<Character>
@Query("SELECT * FROM Character WHERE dimId = :dimId")
suspend fun getCharacterByDimId(dimId: Int): List<Character>
@Query("SELECT * FROM Character WHERE monIndex = :monIndex AND dimId = :dimId LIMIT 1")
fun getCharacterByMonIndex(monIndex: Int, dimId: Long): Character
@Insert
suspend fun insertSprite(vararg sprite: Sprites)
@Query("SELECT * FROM Sprites")
suspend fun getAllSprites(): List<Sprites>
suspend fun insertSprite(vararg sprite: Sprite)
@Query(
"""
SELECT
d.dimId as cardId,
c.monIndex as charId
d.cardId as cardId,
c.monIndex as charId,
c.stage as stage,
c.attribute as attribute
FROM Character c
JOIN UserCharacter uc ON c.id = uc.charId
JOIN Card d ON c.dimId = d.id
WHERE uc.id = :charId
WHERE c.id = :charId
"""
)
suspend fun getCharacterInfo(charId: Long): CharacterDtos.DiMInfo
@Query("""
INSERT INTO TransformationHistory(monId, stageId, transformationDate)
VALUES
(:monId,
(SELECT id FROM Character WHERE monIndex = :stage AND dimId = :dimId),
:transformationDate)
""")
fun insertTransformation(monId: Long, stage: Int, dimId: Long, transformationDate: Long)
suspend fun getCharacterInfo(charId: Long): CharacterDtos.CardCharacterInfo
}
@@ -19,11 +19,12 @@ interface DexDao {
@Query("""
SELECT
c.id AS id,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
s.spriteIdle1 AS spriteIdle,
s.width AS spriteWidth,
s.height AS spriteHeight,
d.discoveredOn AS discoveredOn
FROM character c
FROM Character c
JOIN Sprite s ON c.spriteId = s.id
LEFT JOIN dex d ON c.id = d.id
WHERE c.dimId = :cardId
""")
@@ -1,11 +0,0 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.DimProgress
@Dao
interface DiMProgressDao {
@Upsert
suspend fun updateDimProgress(vararg dimProgress: DimProgress)
}
@@ -6,40 +6,48 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
@Dao
interface ItemDao {
@Query("""
SELECT Items.*, UserItems.quantity
@Query(
"""
SELECT *
FROM Items
LEFT JOIN UserItems ON Items.id = UserItems.itemId
ORDER BY Items.itemIcon ASC
""")
"""
)
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities>
@Query("""
SELECT Items.*, UserItems.quantity
@Query(
"""
SELECT *
FROM Items
JOIN UserItems ON Items.id = UserItems.itemId
""")
WHERE quantity > 0
"""
)
suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>
@Query("""
SELECT Items.*, UserItems.quantity
@Query(
"""
SELECT *
FROM Items
JOIN UserItems ON Items.id = UserItems.itemId
WHERE UserItems.itemId = :itemId
""")
fun getUserItem(itemId: Long): ItemDtos.ItemsWithQuantities
WHERE Items.id = :itemId
"""
)
suspend fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities
@Query("""
UPDATE UserItems
@Query(
"""
UPDATE Items
SET quantity = quantity - 1
WHERE itemId = :itemId
""")
fun useItem(itemId: Long)
WHERE id = :itemId
"""
)
suspend fun useItem(itemId: Long)
@Query("""
UPDATE UserItems
SET quantity = quantity - :itemAmount
WHERE itemId = :itemId
""")
@Query(
"""
UPDATE Items
SET quantity = quantity + :itemAmount
WHERE id = :itemId
"""
)
suspend fun purchaseItem(itemId: Long, itemAmount: Int)
}
@@ -0,0 +1,15 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
@Dao
interface SpecialMissionDao {
@Query("""
UPDATE SpecialMissions SET
missionType = "NONE",
status = "UNAVAILABLE"
WHERE id = :id
""")
suspend fun clearSpecialMission(id: Long)
}
@@ -0,0 +1,16 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.characters.Sprite
@Dao
interface SpriteDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertSprite(sprite: Sprite): Long
@Query("SELECT * FROM Sprite")
suspend fun getAllSprites(): List<Sprite>
}
@@ -5,9 +5,13 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.characters.Character
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.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@Dao
@@ -18,6 +22,9 @@ interface UserCharacterDao {
@Insert
fun insertBECharacterData(characterData: BECharacterData)
@Insert
fun insertVBCharacterData(characterData: VBCharacterData)
@Upsert
fun updateCharacter(character: UserCharacter)
@@ -27,56 +34,107 @@ interface UserCharacterDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertTransformationHistory(vararg transformationHistory: TransformationHistory)
@Upsert
fun insertSpecialMissions(vararg specialMissions: SpecialMissions)
@Query("""
SELECT
c.id AS id,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
s.spriteIdle1 AS spriteIdle,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.monIndex AS monIndex,
t.transformationDate AS transformationDate
FROM TransformationHistory t
JOIN Character c ON c.id = t.stageId
JOIN Sprite s ON s.id = c.spriteId
WHERE monId = :monId
""")
fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?
suspend fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?
@Query(
"""
SELECT
uc.*,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.isBEm as isBemCard
c.stage,
c.attribute,
s.spriteIdle1 AS spriteIdle,
s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.name as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id
JOIN Card d ON d.id = c.dimId
JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id
"""
)
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
@Query(
"""
SELECT
uc.*,
c.stage,
c.attribute,
s.spriteIdle1 AS spriteIdle,
s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.name as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id
JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.id = :id
""")
suspend fun getCharacterWithSprites(id: Long): CharacterDtos.CharacterWithSprites
@Query("SELECT * FROM UserCharacter WHERE id = :id")
suspend fun getCharacter(id: Long): UserCharacter
@Query("SELECT * FROM BECharacterData WHERE id = :id")
suspend fun getBeData(id: Long): BECharacterData
@Query("SELECT * FROM VBCharacterData WHERE id = :id")
suspend fun getVbData(id: Long): VBCharacterData
@Query("SELECT * FROM SpecialMissions WHERE characterId = :id")
suspend fun getSpecialMissions(id: Long): List<SpecialMissions>
@Query(
"""
SELECT
uc.*,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.isBEm as isBemCard
c.stage,
c.attribute,
s.spriteIdle1 AS spriteIdle,
s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.name as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId as isInAdventure
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id
JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.isActive = 1
LIMIT 1
"""
)
""")
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites?
@Query("DELETE FROM UserCharacter WHERE id = :id")
@@ -87,4 +145,81 @@ interface UserCharacterDao {
@Query("UPDATE UserCharacter SET isActive = 1 WHERE id = :id")
fun setActiveCharacter(id: Long)
@Query(
"""
SELECT c.*
FROM Character c
join UserCharacter uc on c.id = uc.charId
where uc.id = :charId
LIMIT 1
"""
)
suspend fun getCharacterInfo(charId: Long): Character
@Query("""
INSERT INTO TransformationHistory(monId, stageId, transformationDate)
VALUES
(:monId,
(SELECT id FROM Character WHERE monIndex = :stage AND dimId = :dimId),
:transformationDate)
""")
fun insertTransformation(monId: Long, stage: Int, dimId: Long, transformationDate: Long)
@Upsert
fun insertVitals(vararg vitalsHistory: VitalsHistory)
@Query("""SELECT * FROM VitalsHistory WHERE charId = :charId ORDER BY id ASC""")
suspend fun getVitalsHistory(charId: Long): List<VitalsHistory>
@Query(
"""
SELECT
uc.*,
c.stage,
c.attribute,
s.spriteIdle1 AS spriteIdle,
s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.name as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON d.id = c.dimId
JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE d.isBEm = 1
"""
)
suspend fun getBEBemCharacters(): List<CharacterDtos.CharacterWithSprites>
@Query(
"""
SELECT
uc.*,
c.stage,
c.attribute,
s.spriteIdle1 AS spriteIdle,
s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.name as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON d.id = c.dimId
JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.characterType = "VBDevice"
"""
)
suspend fun getVBDimCharacters(): List<CharacterDtos.CharacterWithSprites>
}
@@ -2,39 +2,55 @@ package com.github.nacabaro.vbhelper.database
import androidx.room.Database
import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.daos.AdventureDao
import com.github.nacabaro.vbhelper.daos.CharacterDao
import com.github.nacabaro.vbhelper.daos.DexDao
import com.github.nacabaro.vbhelper.daos.DiMDao
import com.github.nacabaro.vbhelper.daos.CardDao
import com.github.nacabaro.vbhelper.daos.CardProgressDao
import com.github.nacabaro.vbhelper.daos.ItemDao
import com.github.nacabaro.vbhelper.daos.SpecialMissionDao
import com.github.nacabaro.vbhelper.daos.SpriteDao
import com.github.nacabaro.vbhelper.daos.UserCharacterDao
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.domain.characters.Adventure
import com.github.nacabaro.vbhelper.domain.characters.Dex
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory
import com.github.nacabaro.vbhelper.domain.items.Items
import com.github.nacabaro.vbhelper.domain.items.UserItems
@Database(
version = 1,
entities = [
Card::class,
CardProgress::class,
Character::class,
Sprites::class,
Sprite::class,
UserCharacter::class,
BECharacterData::class,
VBCharacterData::class,
SpecialMissions::class,
TransformationHistory::class,
VitalsHistory::class,
Dex::class,
Items::class,
UserItems::class
Adventure::class
]
)
abstract class AppDatabase : RoomDatabase() {
abstract fun dimDao(): DiMDao
abstract fun cardDao(): CardDao
abstract fun cardProgressDao(): CardProgressDao
abstract fun characterDao(): CharacterDao
abstract fun userCharacterDao(): UserCharacterDao
abstract fun dexDao(): DexDao
abstract fun itemDao(): ItemDao
abstract fun adventureDao(): AdventureDao
abstract fun spriteDao(): SpriteDao
abstract fun specialMissionDao(): SpecialMissionDao
}
@@ -1,29 +0,0 @@
package com.github.nacabaro.vbhelper.domain
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.characters.Card
@Entity(
foreignKeys = [
ForeignKey(
entity = User::class,
parentColumns = ["id"],
childColumns = ["userId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Card::class,
parentColumns = ["id"],
childColumns = ["dimId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class DimProgress(
@PrimaryKey val dimId: Int,
@PrimaryKey val userId: Int,
val currentStage: Int,
val unlocked: Boolean
)
@@ -1,12 +0,0 @@
package com.github.nacabaro.vbhelper.domain
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Sprites(
@PrimaryKey(autoGenerate = true) val id : Int,
val sprite: ByteArray,
val width: Int,
val height: Int
)
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.domain.characters
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.PrimaryKey
@@ -7,7 +7,7 @@ import androidx.room.PrimaryKey
data class Card(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val dimId: Int,
val cardId: Int,
val logo: ByteArray,
val logoWidth: Int,
val logoHeight: Int,
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.domain.items
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
@@ -7,14 +7,15 @@ import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = Items::class,
entity = Card::class,
parentColumns = ["id"],
childColumns = ["itemId"],
childColumns = ["cardId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class UserItems(
@PrimaryKey val itemId: Long,
val quantity: Int,
data class CardProgress(
@PrimaryKey val cardId: Long,
val currentStage: Int,
val unlocked: Boolean
)
@@ -0,0 +1,22 @@
package com.github.nacabaro.vbhelper.domain.characters
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
@Entity(
foreignKeys = [
ForeignKey(
entity = UserCharacter::class,
parentColumns = ["id"],
childColumns = ["characterId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Adventure(
@PrimaryKey val characterId: Long,
val originalDuration: Long,
val finishesAdventure: Long
)
@@ -3,6 +3,8 @@ package com.github.nacabaro.vbhelper.domain.characters
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.ForeignKey
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.domain.card.Card
@Entity(
foreignKeys = [
@@ -11,23 +13,32 @@ import androidx.room.ForeignKey
parentColumns = ["id"],
childColumns = ["dimId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Sprite::class,
parentColumns = ["id"],
childColumns = ["spriteId"],
onDelete = ForeignKey.CASCADE
)
]
)
/*
* Character represents a character on a card. There should only be one of these per dimId
* and monIndex.
* TODO: Customs will mean this should be unique per cardName and monIndex
*/
data class Character (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val dimId: Long,
val spriteId: Long,
val monIndex: Int,
val name: ByteArray,
val stage: Int, // These should be replaced with enums
val attribute: Int, // This one too
val attribute: NfcCharacter.Attribute, // This one too
val baseHp: Int,
val baseBp: Int,
val baseAp: Int,
val sprite1: ByteArray,
val sprite2: ByteArray,
val nameWidth: Int,
val nameHeight: Int,
val spritesWidth: Int,
val spritesHeight: Int
val nameHeight: Int
)
@@ -0,0 +1,23 @@
package com.github.nacabaro.vbhelper.domain.characters
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Sprite(
@PrimaryKey(autoGenerate = true) val id : Int = 0,
val spriteIdle1: ByteArray,
val spriteIdle2: ByteArray,
val spriteWalk1: ByteArray,
val spriteWalk2: ByteArray,
val spriteRun1: ByteArray,
val spriteRun2: ByteArray,
val spriteTrain1: ByteArray,
val spriteTrain2: ByteArray,
val spriteHappy: ByteArray,
val spriteSleep: ByteArray,
val spriteAttack: ByteArray,
val spriteDodge: ByteArray,
val width: Int,
val height: Int
)
@@ -0,0 +1,28 @@
package com.github.nacabaro.vbhelper.domain.device_data
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.vb.SpecialMission
@Entity(
foreignKeys = [
ForeignKey(
entity = UserCharacter::class,
parentColumns = ["id"],
childColumns = ["characterId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class SpecialMissions (
@PrimaryKey(autoGenerate = true) var id: Long = 0,
var characterId: Long,
var goal: Int,
val watchId: Int,
val progress: Int,
val status: SpecialMission.Status,
val timeElapsedInMinutes: Int,
val timeLimitInMinutes: Int,
val missionType: SpecialMission.Type
)
@@ -17,13 +17,13 @@ import com.github.nacabaro.vbhelper.domain.characters.Character
)
]
)
/**
* UserCharacter represents and instance of a character. The charId should map to a Character
*/
data class UserCharacter (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
var charId: Long,
var stage: Int,
var attribute: NfcCharacter.Attribute,
var ageInDays: Int,
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
var mood: Int,
var vitalPoints: Int,
var transformationCountdown: Int,
@@ -1,4 +1,21 @@
package com.github.nacabaro.vbhelper.domain.device_data
class VBCharacterData {
}
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = UserCharacter::class,
parentColumns = ["id"],
childColumns = ["id"],
onDelete = ForeignKey.CASCADE
)
]
)
data class VBCharacterData (
@PrimaryKey val id: Long,
val generation: Int,
val totalTrophies: Int
)
@@ -0,0 +1,24 @@
package com.github.nacabaro.vbhelper.domain.device_data
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = UserCharacter::class,
parentColumns = ["id"],
childColumns = ["charId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class VitalsHistory (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val charId: Long,
val year: Int,
val month: Int,
val day: Int,
val vitalPoints: Int
)
@@ -3,6 +3,13 @@ package com.github.nacabaro.vbhelper.domain.items
import androidx.room.Entity
import androidx.room.PrimaryKey
enum class ItemType {
VBITEM,
BEITEM,
UNIVERSAL,
SPECIALMISSION
}
@Entity
data class Items(
@PrimaryKey val id: Long,
@@ -10,5 +17,7 @@ data class Items(
val description: String,
val itemIcon: Int,
val itemLength: Int,
val price: Int
val price: Int,
val quantity: Int,
val itemType: ItemType
)
@@ -11,7 +11,6 @@ object CharacterDtos {
var stage: Int,
var attribute: NfcCharacter.Attribute,
var ageInDays: Int,
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
var mood: Int,
var vitalPoints: Int,
var transformationCountdown: Int,
@@ -25,14 +24,21 @@ object CharacterDtos {
var heartRateCurrent: Int,
var characterType: DeviceType,
val spriteIdle: ByteArray,
val spriteIdle2: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val isBemCard: Boolean
val nameSprite: ByteArray,
val nameSpriteWidth: Int,
val nameSpriteHeight: Int,
val isBemCard: Boolean,
val isInAdventure: Boolean
)
data class DiMInfo(
data class CardCharacterInfo(
val cardId: Int,
val charId: Int
val charId: Int,
val stage: Int,
val attribute: NfcCharacter.Attribute
)
data class TransformationHistory(
@@ -51,4 +57,30 @@ object CharacterDtos {
val spriteHeight: Int,
val discoveredOn: Long?
)
data class AdventureCharacterWithSprites(
var id: Long = 0,
var charId: Long,
var stage: Int,
var attribute: NfcCharacter.Attribute,
var ageInDays: Int,
var mood: Int,
var vitalPoints: Int,
var transformationCountdown: Int,
var injuryStatus: NfcCharacter.InjuryStatus,
var trophies: Int,
var currentPhaseBattlesWon: Int,
var currentPhaseBattlesLost: Int,
var totalBattlesWon: Int,
var totalBattlesLost: Int,
var activityLevel: Int,
var heartRateCurrent: Int,
var characterType: DeviceType,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val isBemCard: Boolean,
val finishesAdventure: Long,
val originalTimeInMinutes: Long
)
}
@@ -1,8 +1,10 @@
package com.github.nacabaro.vbhelper.dtos
import com.github.nacabaro.vbhelper.domain.items.ItemType
object ItemDtos {
data class ItemsWithQuantities (
data class ItemsWithQuantities(
val id: Long,
val name: String,
val description: String,
@@ -10,5 +12,16 @@ object ItemDtos {
val itemLength: Int,
val price: Int,
val quantity: Int,
val itemType: ItemType
)
data class PurchasedItem(
val itemId: Long,
val itemName: String,
val itemDescription: String,
val itemIcon: Int,
val itemLength: Int,
val itemAmount: Int,
val itemType: ItemType
)
}
@@ -1,12 +1,23 @@
package com.github.nacabaro.vbhelper.navigation
import android.util.Log
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.screens.BattlesScreen
import com.github.nacabaro.vbhelper.screens.DexScreen
import com.github.nacabaro.vbhelper.screens.DiMScreen
@@ -15,16 +26,27 @@ import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreen
import com.github.nacabaro.vbhelper.screens.SpriteViewer
import com.github.nacabaro.vbhelper.screens.StorageScreen
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewer
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ChooseCharacterScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreen
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository
data class AppNavigationHandlers(
val settingsScreenController: SettingsScreenControllerImpl,
val scanScreenController: ScanScreenControllerImpl,
val itemsScreenController: ItemsScreenControllerImpl
val itemsScreenController: ItemsScreenControllerImpl,
val adventureScreenController: AdventureScreenControllerImpl,
val storageScreenController: StorageScreenControllerImpl,
val homeScreenController: HomeScreenControllerImpl,
val spriteViewerController: SpriteViewerControllerImpl
)
@Composable
@@ -41,30 +63,61 @@ fun AppNavigation(
NavHost(
navController = navController,
startDestination = NavigationItems.Home.route,
enterTransition = {
fadeIn(
animationSpec = tween(200)
)
},
exitTransition = {
fadeOut(
animationSpec = tween(200)
)
},
modifier = Modifier
.padding(contentPadding)
) {
composable(NavigationItems.Battles.route) {
BattlesScreen()
}
composable(NavigationItems.Home.route) {
HomeScreen(
navController = navController
navController = navController,
homeScreenController = applicationNavigationHandlers.homeScreenController
)
}
composable(NavigationItems.Storage.route) {
StorageScreen(
navController = navController
navController = navController,
adventureScreenController = applicationNavigationHandlers.adventureScreenController,
storageScreenController = applicationNavigationHandlers.storageScreenController
)
}
composable(NavigationItems.Scan.route) {
val characterIdString = it.arguments?.getString("characterId")
val characterId = characterIdString?.toLongOrNull()
var characterId by remember { mutableStateOf(characterIdString?.toLongOrNull()) }
Log.d("ScanScreen", "characterId: $characterId")
val launchedFromHomeScreen = (characterIdString?.toLongOrNull() == null)
if (characterId == null) {
val context = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(context.container.db)
LaunchedEffect(characterId) {
if (characterId == null) {
val characterData = storageRepository.getActiveCharacter()
if (characterData != null) {
characterId = characterData.id
}
}
}
}
ScanScreen(
navController = navController,
scanScreenController = applicationNavigationHandlers.scanScreenController,
characterId = characterId
characterId = characterId,
launchedFromHomeScreen = launchedFromHomeScreen
)
}
composable(NavigationItems.Dex.route) {
@@ -80,7 +133,8 @@ fun AppNavigation(
}
composable(NavigationItems.Viewer.route) {
SpriteViewer(
navController = navController
navController = navController,
spriteViewerController = applicationNavigationHandlers.spriteViewerController
)
}
composable(NavigationItems.CardView.route) {
@@ -108,6 +162,18 @@ fun AppNavigation(
)
}
}
composable(NavigationItems.Adventure.route) {
AdventureScreen(
navController = navController,
storageScreenController = applicationNavigationHandlers
.adventureScreenController
)
}
composable(NavigationItems.Credits.route) {
CreditsScreen(
navController = navController
)
}
}
}
}
@@ -19,6 +19,7 @@ fun BottomNavigationBar(navController: NavController) {
NavigationItems.Dex,
NavigationItems.Storage,
)
NavigationBar {
val currentBackStackEntry = navController.currentBackStackEntryAsState()
val currentRoute = currentBackStackEntry.value?.destination?.route
@@ -19,4 +19,6 @@ sealed class NavigationItems (
object MyItems : NavigationItems("MyItems", R.drawable.baseline_data_24, "My items")
object ItemsStore : NavigationItems("ItemsStore", R.drawable.baseline_data_24, "Items store")
object ApplyItem : NavigationItems("ApplyItem/{itemId}", R.drawable.baseline_data_24, "Apply item")
object Adventure : NavigationItems("Adventure", R.drawable.baseline_fort_24, "Adventure")
object Credits : NavigationItems("Credits", R.drawable.baseline_data_24, "Credits")
}
@@ -1,9 +1,33 @@
package com.github.nacabaro.vbhelper.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.github.nacabaro.vbhelper.components.TopBanner
@Composable
fun BattlesScreen() {
Text("Battles Screen")
Scaffold (
topBar = {
TopBanner(
text = "Online battles"
)
}
) { contentPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text("Coming soon")
}
}
}
@@ -18,7 +18,6 @@ import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.DexDiMEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.DexRepository
@@ -1,194 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun StorageScreen(
navController: NavController
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) }
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
val characterList = storageRepository.getAllCharacters()
monList.value = characterList
}
}
Scaffold (
topBar = { TopBanner(text = "My characters") }
) { contentPadding ->
if (monList.value.isEmpty()) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(
text = "Nothing to see here",
textAlign = TextAlign.Center,
modifier = Modifier
)
}
}
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding())
) {
items(monList.value) { index ->
CharacterEntry(
icon = BitmapData(
bitmap = index.spriteIdle,
width = index.spriteWidth,
height = index.spriteHeight
),
onClick = {
selectedCharacter = index.id
}
)
}
}
if (selectedCharacter != null) {
StorageDialog(
characterId = selectedCharacter!!,
onDismissRequest = { selectedCharacter = null },
onClickSetActive = {
coroutineScope.launch {
withContext(Dispatchers.IO) {
storageRepository.setActiveCharacter(selectedCharacter!!)
selectedCharacter = null
}
navController.navigate(NavigationItems.Home.route)
}
},
onSendToBracelet = {
navController.navigate(
NavigationItems.Scan.route.replace(
"{characterId}",
selectedCharacter.toString()
)
)
}
)
}
}
}
@Composable
fun StorageDialog(
characterId: Long,
onDismissRequest: () -> Unit,
onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val character = remember { mutableStateOf<UserCharacter?>(null) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
character.value = storageRepository.getSingleCharacter(characterId)
}
}
Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
Card(
shape = RoundedCornerShape(16.dp)
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
if (character.value != null) {
Text(
text = character.value?.toString() ?: "Loading...",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(8.dp)
)
}
Row (
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
Button(
onClick = onSendToBracelet
) {
Text(text = "Send to bracelet")
}
Button(
onClick = onClickSetActive
) {
Text(text = "Set active")
}
Button(
onClick = onDismissRequest
) {
Text(text = "Close")
}
}
}
}
}
}
@@ -0,0 +1,68 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
import java.util.Locale
@Composable
fun AdventureEntry(
icon: BitmapData,
timeLeft: Long,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
val bitmap = remember (icon.bitmap) { icon.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (icon.width * 4 / density).dp
Card(
onClick = onClick,
modifier = modifier
.padding(8.dp)
.fillMaxWidth()
) {
Row(
modifier = Modifier
.padding(8.dp)
.height(96.dp)
) {
Image(
bitmap = imageBitmap,
contentDescription = null,
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
)
Text(
text = when {
timeLeft < 0 -> "Adventure finished"
else -> "Time left: ${formatSeconds(timeLeft)}"
}
)
}
}
}
fun formatSeconds(totalSeconds: Long): String {
val hours = totalSeconds / 3600
val minutes = (totalSeconds % 3600) / 60
val seconds = totalSeconds % 60
return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds)
}
@@ -0,0 +1,145 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.Instant
@Composable
fun AdventureScreen(
navController: NavController,
storageScreenController: AdventureScreenControllerImpl
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val storageRepository = StorageRepository(database)
val characterList = remember {
mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
}
var obtainedItem by remember {
mutableStateOf<ItemDtos.PurchasedItem?>(null)
}
val currentTime by produceState(initialValue = Instant.now().epochSecond) {
while (true) {
value = Instant.now().epochSecond
delay(1000)
}
}
var cancelAdventureDialog by remember {
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
}
LaunchedEffect(storageRepository) {
coroutineScope.launch {
characterList.value = storageRepository
.getAdventureCharacters()
}
}
Scaffold(
topBar = {
TopBanner(
text = "Adventure",
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
if (characterList.value.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(text = "Nothing to see here")
}
} else {
LazyColumn(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(characterList.value) {
AdventureEntry(
icon = BitmapData(
bitmap = it.spriteIdle,
width = it.spriteWidth,
height = it.spriteHeight
),
timeLeft = it.finishesAdventure - currentTime,
onClick = {
if (it.finishesAdventure < currentTime) {
storageScreenController
.getItemFromAdventure(it.id) { adventureResult ->
obtainedItem = adventureResult
}
} else {
cancelAdventureDialog = it
}
}
)
}
}
}
}
if (obtainedItem != null) {
ObtainedItemDialog(
obtainedItem = obtainedItem!!,
onClickDismiss = {
obtainedItem = null
}
)
}
if (cancelAdventureDialog != null) {
CancelAdventureDialog(
characterSprite = BitmapData(
bitmap = cancelAdventureDialog!!.spriteIdle,
width = cancelAdventureDialog!!.spriteWidth,
height = cancelAdventureDialog!!.spriteHeight
),
onDismissRequest = {
cancelAdventureDialog = null
},
onClickConfirm = {
storageScreenController.cancelAdventure(cancelAdventureDialog!!.id) {
navController.navigate(NavigationItems.Storage.route)
}
cancelAdventureDialog = null
}
)
}
}
@@ -0,0 +1,9 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface AdventureScreenController {
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit)
fun cancelAdventure(characterId: Long, onResult: () -> Unit)
}
@@ -0,0 +1,101 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
import kotlin.random.Random
class AdventureScreenControllerImpl(
private val componentActivity: ComponentActivity,
) : AdventureScreenController {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
override fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long) {
val finishesAdventureAt = timeInMinutes * 60
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
val characterData = database
.userCharacterDao()
.getCharacter(characterId)
if (characterData.isActive) {
database
.userCharacterDao()
.clearActiveCharacter()
}
database
.adventureDao()
.insertNewAdventure(characterId, timeInMinutes, finishesAdventureAt)
}
}
override fun getItemFromAdventure(
characterId: Long,
onResult: (ItemDtos.PurchasedItem) -> Unit
) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.adventureDao()
.deleteAdventure(characterId)
val generatedItem = generateItem(characterId)
onResult(generatedItem)
}
}
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.adventureDao()
.deleteAdventure(characterId)
componentActivity
.runOnUiThread {
Toast.makeText(
componentActivity,
"Adventure canceled",
Toast.LENGTH_SHORT
).show()
onResult()
}
}
}
private suspend fun generateItem(characterId: Long): ItemDtos.PurchasedItem {
val character = database
.userCharacterDao()
.getCharacterInfo(characterId)
val randomItem = database
.itemDao()
.getAllItems()
.random()
val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()
database
.itemDao()
.purchaseItem(
itemId = randomItem.id,
itemAmount = random
)
return ItemDtos.PurchasedItem(
itemId = randomItem.id,
itemAmount = random,
itemName = randomItem.name,
itemIcon = randomItem.itemIcon,
itemLength = randomItem.itemLength,
itemDescription = randomItem.description,
itemType = randomItem.itemType
)
}
}
@@ -0,0 +1,73 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
@Composable
fun CancelAdventureDialog(
characterSprite: BitmapData,
onDismissRequest: () -> Unit,
onClickConfirm: () -> Unit
) {
val bitmap = remember (characterSprite) { characterSprite.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (characterSprite.width * 4 / density).dp
Dialog(
onDismissRequest = onDismissRequest
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Row {
Image(
bitmap = imageBitmap,
contentDescription = null,
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
)
Text(
text = "Are you sure you want to cancel this character's adventure?"
)
}
Row(
modifier = Modifier
.padding(8.dp)
) {
Button(
onClick = onClickConfirm
) {
Text(text = "Confirm")
}
Spacer(modifier = Modifier.padding(4.dp))
Button(
onClick = onDismissRequest
) {
Text(text = "Dismiss")
}
}
}
}
}
}
@@ -0,0 +1,46 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.compose.foundation.layout.Column
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.unit.dp
import androidx.compose.ui.window.Dialog
@Composable
fun BetaWarning(
onDismissRequest: () -> Unit
) {
Dialog(
onDismissRequest = onDismissRequest
) {
Card {
Column (
modifier = Modifier
.padding(16.dp)
) {
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!"
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = "Application should work now with the original VB and the VH."
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = "Thank you for your understanding and patience. Sincerely, the dev team."
)
Spacer(modifier = Modifier.padding(8.dp))
Button(
onClick = onDismissRequest
) {
Text(text = "Dismiss")
}
}
}
}
}
@@ -1,33 +1,50 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.utils.DeviceType
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.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEBEmHomeScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.VBDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun HomeScreen(
navController: NavController
navController: NavController,
homeScreenController: HomeScreenControllerImpl
) {
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
@@ -35,17 +52,32 @@ fun HomeScreen(
val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
val beData = remember { mutableStateOf<BECharacterData?>(null) }
val vbData = remember { mutableStateOf<VBCharacterData?>(null) }
val vbSpecialMissions = remember { mutableStateOf<List<SpecialMissions>>(emptyList()) }
var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }
var betaWarning by rememberSaveable { mutableStateOf(true) }
var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) }
LaunchedEffect(storageRepository, activeMon) {
LaunchedEffect(storageRepository, activeMon, collectedItem) {
withContext(Dispatchers.IO) {
activeMon.value = storageRepository.getActiveCharacter()
if (activeMon.value != null) {
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) {
homeScreenController
.didAdventureMissionsFinish {
adventureMissionsFinished = it
}
}
Scaffold (
topBar = {
TopBanner(
@@ -60,6 +92,7 @@ fun HomeScreen(
}
) { contentPadding ->
if (activeMon.value == null || (beData.value == null && vbData.value == null) || transformationHistory.value == null) {
Log.d("TetTet", "Something is null")
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
@@ -70,6 +103,7 @@ fun HomeScreen(
Text(text = "Nothing to see here")
}
} else {
Log.d("TetTet", "Something is not null")
if (activeMon.value!!.isBemCard) {
BEBEmHomeScreen(
activeMon = activeMon.value!!,
@@ -89,11 +123,59 @@ fun HomeScreen(
activeMon = activeMon.value!!,
vbData = vbData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
contentPadding = contentPadding,
specialMissions = vbSpecialMissions.value,
homeScreenController = homeScreenController,
onClickCollect = { item ->
collectedItem = item
}
)
}
}
}
if (collectedItem != null) {
ObtainedItemDialog(
obtainedItem = collectedItem!!,
onClickDismiss = {
collectedItem = null
}
)
}
if (adventureMissionsFinished) {
Dialog(
onDismissRequest = { adventureMissionsFinished = false },
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = "One of your characters has finished their adventure mission!",
textAlign = TextAlign.Center
)
Button(
onClick = {
adventureMissionsFinished = false
},
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text(text = "Dismiss")
}
}
}
}
}
if (betaWarning) {
BetaWarning {
betaWarning = false
}
}
}
@@ -0,0 +1,8 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface HomeScreenController {
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem) -> Unit)
}
@@ -0,0 +1,66 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.launch
import java.time.Instant
import kotlin.math.roundToInt
import kotlin.random.Random
class HomeScreenControllerImpl(
private val componentActivity: ComponentActivity,
): HomeScreenController {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
override fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit) {
componentActivity.lifecycleScope.launch {
val currentTime = Instant.now().epochSecond
val adventureCharacters = database
.adventureDao()
.getAdventureCharacters()
val finishedAdventureCharacters = adventureCharacters.filter { character ->
character.finishesAdventure <= currentTime
}
onCompletion(finishedAdventureCharacters.isNotEmpty())
}
}
override fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem) -> Unit) {
componentActivity.lifecycleScope.launch {
database
.specialMissionDao()
.clearSpecialMission(missionId)
val randomItem = database
.itemDao()
.getAllItems()
.random()
val randomItemAmount = (Random.nextFloat() * 5).roundToInt()
database
.itemDao()
.purchaseItem(
itemId = randomItem.id,
itemAmount = randomItemAmount
)
val purchasedItem = ItemDtos.PurchasedItem(
itemId = randomItem.id,
itemName = randomItem.name,
itemDescription = randomItem.description,
itemIcon = randomItem.itemIcon,
itemLength = randomItem.itemLength,
itemAmount = randomItemAmount,
itemType = randomItem.itemType
)
onCleared(purchasedItem)
}
}
}
@@ -1,16 +0,0 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@Composable
fun VBDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
vbData: VBCharacterData,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues
) {
TODO("Not implemented yet")
}
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
package com.github.nacabaro.vbhelper.screens.homeScreens.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -15,7 +15,7 @@ import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.ItemDisplay
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.screens.itemsScreen.getIconResource
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
package com.github.nacabaro.vbhelper.screens.homeScreens.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -16,7 +16,7 @@ import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.ItemDisplay
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.screens.itemsScreen.getIconResource
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
@@ -0,0 +1,189 @@
package com.github.nacabaro.vbhelper.screens.homeScreens.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.ItemDisplay
import com.github.nacabaro.vbhelper.components.SpecialMissionsEntry
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.utils.BitmapData
import java.util.Locale
@Composable
fun VBDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
vbData: VBCharacterData,
specialMissions: List<SpecialMissions>,
homeScreenController: HomeScreenControllerImpl,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues,
onClickCollect: (ItemDtos.PurchasedItem) -> Unit
) {
Column(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.verticalScroll(state = rememberScrollState())
) {
Row(
modifier = Modifier
.fillMaxWidth()
) {
CharacterEntry(
icon = BitmapData(
bitmap = activeMon.spriteIdle,
width = activeMon.spriteWidth,
height = activeMon.spriteHeight
),
multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
)
Column(
modifier = Modifier
.weight(0.5f)
.aspectRatio(0.5f)
) {
ItemDisplay(
icon = R.drawable.baseline_vitals_24,
textValue = activeMon.vitalPoints.toString(),
definition = "Vitals",
modifier = Modifier
.weight(0.5f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_trophy_24,
textValue = activeMon.trophies.toString(),
definition = "Trophies",
modifier = Modifier
.weight(0.5f)
.aspectRatio(1f)
.padding(8.dp)
)
}
}
Row(
modifier = Modifier
.fillMaxWidth()
) {
ItemDisplay(
icon = R.drawable.baseline_mood_24,
textValue = activeMon.mood.toString(),
definition = "Mood",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
val transformationCountdownInHours = activeMon.transformationCountdown / 60
ItemDisplay(
icon = R.drawable.baseline_next_24,
textValue = when (transformationCountdownInHours) {
0 -> "${activeMon.transformationCountdown} m"
else -> "$transformationCountdownInHours h"
},
definition = "Next timer",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_swords_24,
textValue = when {
activeMon.totalBattlesLost == 0 -> "0.00 %"
else -> {
val battleWinPercentage =
activeMon.totalBattlesWon.toFloat() / (activeMon.totalBattlesWon + activeMon.totalBattlesLost).toFloat()
String.format(
Locale.getDefault(),
"%.2f",
battleWinPercentage * 100
) + " %" // Specify locale
}
},
definition = "Total battle win %",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_swords_24,
textValue = when {
activeMon.totalBattlesLost == 0 -> "0.00 %"
else -> {
val battleWinPercentage =
activeMon.currentPhaseBattlesWon.toFloat() / (activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost).toFloat()
String.format(
Locale.getDefault(),
"%.2f",
battleWinPercentage * 100
) + " %" // Specify locale
}
},
definition = "Current phase win %",
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(8.dp)
)
}
Row(
modifier = Modifier
.fillMaxWidth()
) {
TransformationHistoryCard(
transformationHistory = transformationHistory,
modifier = Modifier
.weight(1f)
.padding(8.dp)
)
}
Row (
modifier = Modifier
.padding(16.dp)
) {
Text(
text = "Special missions",
fontSize = 24.sp
)
}
for (mission in specialMissions) {
Row(
modifier = Modifier
.fillMaxWidth()
) {
SpecialMissionsEntry(
specialMission = mission,
modifier = Modifier
.weight(1f)
.padding(8.dp),
) {
homeScreenController
.clearSpecialMission(mission.id, onClickCollect)
}
}
}
}
}
@@ -19,7 +19,9 @@ import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.items.ItemType
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.launch
@@ -39,10 +41,25 @@ fun ChooseCharacterScreen(
}
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
var selectedItem by remember { mutableStateOf<ItemDtos.ItemsWithQuantities?>(null) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
characterList.value = storageRepository.getAllCharacters()
selectedItem = storageRepository.getItem(itemId)
when (selectedItem?.itemType) {
ItemType.BEITEM -> {
characterList.value = storageRepository.getBEBEmCharacters()
}
ItemType.VBITEM -> {
characterList.value = storageRepository.getVBCharacters()
}
ItemType.SPECIALMISSION-> {
characterList.value = storageRepository.getVBCharacters()
}
else -> {
characterList.value = storageRepository.getAllCharacters()
}
}
}
}
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.components
package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -25,7 +25,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@Composable
@@ -123,7 +122,10 @@ fun ItemDialog(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
text = description
text = description,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
)
Text(
textAlign = TextAlign.Center,
@@ -132,7 +134,7 @@ fun ItemDialog(
text = "You have $amount of this item",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.padding(8.dp)
)
Row (
horizontalArrangement = Arrangement.Center,
@@ -2,9 +2,12 @@ package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.Dispatchers
@@ -24,7 +27,15 @@ class ItemsScreenControllerImpl (
AllTraining(5),
EvoTimer(6),
LimitTimer(7),
Vitals(8)
Vitals(8),
Step8k(9),
Step4k(10),
Vitals1000(11),
Vitals250(12),
Battle20(13),
Battle5(14),
Win10(15),
Win4(16)
}
init {
@@ -37,17 +48,20 @@ class ItemsScreenControllerImpl (
withContext(Dispatchers.IO) {
val item = getItem(itemId)
val characterData = database.userCharacterDao().getCharacter(characterId)
val beCharacterData: BECharacterData
//var vbCharacterData: VBCharacterData
var beCharacterData: BECharacterData? = null
var vbCharacterData: VBCharacterData? = null
if (characterData.characterType == DeviceType.BEDevice) {
beCharacterData = database.userCharacterDao().getBeData(characterId)
} else {
TODO("Not implemented")
//vbCharacterData = database.userCharacterDao().getVbData(characterId)
} else if (characterData.characterType == DeviceType.VBDevice) {
vbCharacterData = database.userCharacterDao().getVbData(characterId)
}
if (item.itemIcon in 1 .. 5 && characterData.characterType == DeviceType.BEDevice) {
if (
item.itemIcon in 1 .. 5 &&
characterData.characterType == DeviceType.BEDevice &&
beCharacterData != null
) {
beCharacterData.itemType = item.itemIcon
beCharacterData.itemMultiplier = 3
beCharacterData.itemRemainingTime = item.itemLength
@@ -72,7 +86,11 @@ class ItemsScreenControllerImpl (
.userCharacterDao()
.updateCharacter(characterData)
} else if (item.itemIcon == ItemTypes.LimitTimer.id) {
} else if (
item.itemIcon == ItemTypes.LimitTimer.id &&
characterData.characterType == DeviceType.BEDevice &&
beCharacterData != null
) {
beCharacterData.remainingTrainingTimeInMinutes += item.itemLength
if (beCharacterData.remainingTrainingTimeInMinutes > 6000) {
beCharacterData.remainingTrainingTimeInMinutes = 6000
@@ -93,6 +111,12 @@ class ItemsScreenControllerImpl (
database
.userCharacterDao()
.updateCharacter(characterData)
} else if (item.itemIcon in ItemTypes.Step8k.id .. ItemTypes.Win4.id &&
characterData.characterType == DeviceType.VBDevice &&
vbCharacterData != null
) {
applySpecialMission(item.itemIcon, item.itemLength, characterId)
}
consumeItem(item.id)
@@ -104,13 +128,72 @@ class ItemsScreenControllerImpl (
}
}
private fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities {
return database
.itemDao()
.getUserItem(itemId)
private suspend fun applySpecialMission(itemIcon: Int, itemLength: Int, characterId: Long) {
// Hello, it's me, naca! No! I don't like this, I'll see how I can improve it later on...
val specialMissionType = when (itemIcon) {
ItemTypes.Step8k.id -> SpecialMission.Type.STEPS
ItemTypes.Step4k.id -> SpecialMission.Type.STEPS
ItemTypes.Vitals1000.id -> SpecialMission.Type.VITALS
ItemTypes.Vitals250.id -> SpecialMission.Type.VITALS
ItemTypes.Battle20.id -> SpecialMission.Type.BATTLES
ItemTypes.Battle5.id -> SpecialMission.Type.BATTLES
ItemTypes.Win10.id -> SpecialMission.Type.WINS
ItemTypes.Win4.id -> SpecialMission.Type.WINS
else -> SpecialMission.Type.NONE
}
val specialMissionGoal = when (itemIcon) {
ItemTypes.Step8k.id -> 8000
ItemTypes.Step4k.id -> 4000
ItemTypes.Vitals1000.id -> 1000
ItemTypes.Vitals250.id -> 250
ItemTypes.Battle20.id -> 20
ItemTypes.Battle5.id -> 5
ItemTypes.Win10.id -> 10
ItemTypes.Win4.id -> 4
else -> 0
}
val availableSpecialMissions = database
.userCharacterDao()
.getSpecialMissions(characterId)
var firstUnavailableMissionSlot: Long = 0
var watchId = 0
for ((index, mission) in availableSpecialMissions.withIndex()) {
if (
mission.status == SpecialMission.Status.UNAVAILABLE
) {
firstUnavailableMissionSlot = mission.id
watchId = index + 1
}
}
val newSpecialMission = SpecialMissions(
id = firstUnavailableMissionSlot,
characterId = characterId,
missionType = specialMissionType,
goal = specialMissionGoal,
timeLimitInMinutes = itemLength,
watchId = watchId,
status = SpecialMission.Status.AVAILABLE,
progress = 0,
timeElapsedInMinutes = 0
)
database
.userCharacterDao()
.insertSpecialMissions(newSpecialMission)
}
private fun consumeItem(itemId: Long) {
private suspend fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities {
return database
.itemDao()
.getItem(itemId)
}
private suspend fun consumeItem(itemId: Long) {
database
.itemDao()
.useItem(itemId)
@@ -15,10 +15,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.ItemDialog
import com.github.nacabaro.vbhelper.components.ItemElement
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.components.getLengthResource
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.ItemsRepository
@@ -19,10 +19,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.ItemDialog
import com.github.nacabaro.vbhelper.components.ItemElement
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.components.getLengthResource
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
@@ -0,0 +1,100 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.dtos.ItemDtos
@Composable
fun ObtainedItemDialog(
obtainedItem: ItemDtos.PurchasedItem,
onClickDismiss: () -> Unit
) {
Dialog(
onDismissRequest = onClickDismiss
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
Row {
Box(modifier = Modifier) {
Icon(
painter = painterResource(id = getIconResource(obtainedItem.itemIcon)),
contentDescription = null,
modifier = Modifier
.size(96.dp)
.align(Alignment.Center)
)
Icon(
painter = painterResource(id = getLengthResource(obtainedItem.itemLength)),
contentDescription = null,
tint = MaterialTheme.colorScheme.outline,
modifier = Modifier
.size(64.dp)
.align(Alignment.BottomEnd)
)
}
Column (
modifier = Modifier
.padding(16.dp)
) {
Text(
fontSize = MaterialTheme.typography.titleLarge.fontSize,
text = obtainedItem.itemName,
modifier = Modifier
.fillMaxWidth()
)
}
}
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
text = obtainedItem.itemDescription,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
)
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have obtained ${obtainedItem.itemAmount} of this item",
modifier = Modifier
.fillMaxWidth()
.padding(5.dp)
)
Button(
onClick = onClickDismiss,
modifier = Modifier
.fillMaxWidth()
) {
Text(text = "Dismiss")
}
}
}
}
}
}
@@ -34,7 +34,6 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets
import com.github.nacabaro.vbhelper.utils.characterToNfc
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext
@@ -46,45 +45,68 @@ fun ScanScreen(
navController: NavController,
characterId: Long?,
scanScreenController: ScanScreenController,
launchedFromHomeScreen: Boolean
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
var readingScreen by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) }
val context = LocalContext.current
LaunchedEffect(storageRepository) {
withContext(Dispatchers.IO) {
if(characterId != null && nfcCharacter == null) {
nfcCharacter = characterToNfc(context, characterId)
/*
First check if there is a character sent through the navigation system
If there is not, that means we got here through the home screen nfc button
If we got here through the home screen, it does not hurt to check if there is
an active character.
*/
if (characterId != null && nfcCharacter == null) {
nfcCharacter = scanScreenController.characterToNfc(characterId)
}
}
}
DisposableEffect(readingScreen || writingScreen, isDoneSendingCard) {
if(readingScreen) {
scanScreenController.registerActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER, object: ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
var readingScreen by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
override fun onResume() {
scanScreenController.onClickRead(secrets!!) {
isDoneReadingCharacter = true
DisposableEffect(readingScreen) {
if(readingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object: ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
scanScreenController.onClickRead(secrets!!) {
isDoneReadingCharacter = true
}
}
}
})
)
scanScreenController.onClickRead(secrets!!) {
isDoneReadingCharacter = true
}
} else if (writingScreen) {
}
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 {
@@ -105,6 +127,9 @@ fun ScanScreen(
}
}
)
}
if (secrets != null && nfcCharacter != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
@@ -115,8 +140,9 @@ fun ScanScreen(
}
}
}
onDispose {
if(readingScreen || writingScreen) {
if(writingScreen) {
scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
scanScreenController.cancelRead()
}
@@ -158,7 +184,7 @@ fun ScanScreen(
} else {
ChooseConnectOption(
onClickRead = when {
characterId != null -> null
!launchedFromHomeScreen -> null
else -> {
{
if(secrets == null) {
@@ -184,7 +210,8 @@ fun ScanScreen(
}
}
}
}
},
navController = navController
)
}
}
@@ -193,9 +220,17 @@ fun ScanScreen(
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
navController: NavController
) {
Scaffold(
topBar = { TopBanner(text = "Scan a Vital Bracelet") }
topBar = {
TopBanner(
text = "Scan a Vital Bracelet",
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
@@ -259,7 +294,10 @@ fun ScanScreenPreview() {
override fun onClickCheckCard(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
override fun onClickWrite(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
override fun cancelRead() {}
override fun characterFromNfc(nfcCharacter: NfcCharacter): String { return "" }
override suspend fun characterToNfc(characterId: Long): NfcCharacter? { return null }
},
characterId = null
characterId = null,
launchedFromHomeScreen = false
)
}
@@ -15,4 +15,7 @@ interface ScanScreenController {
fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener)
fun unregisterActivityLifecycleListener(key: String)
fun characterFromNfc(nfcCharacter: NfcCharacter): String
suspend fun characterToNfc(characterId: Long): NfcCharacter?
}
@@ -15,18 +15,15 @@ import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.FromNfcConverter
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.ToNfcConverter
import com.github.nacabaro.vbhelper.source.getCryptographicTransformerMap
import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets
import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.util.GregorianCalendar
class ScanScreenControllerImpl(
override val secretsFlow: Flow<Secrets>,
@@ -49,7 +46,7 @@ class ScanScreenControllerImpl(
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {
handleTag(secrets) { tagCommunicator ->
val character = tagCommunicator.receiveCharacter()
val resultMessage = addCharacterScannedIntoDatabase(character)
val resultMessage = characterFromNfc(character)
onComplete.invoke()
resultMessage
}
@@ -123,7 +120,15 @@ class ScanScreenControllerImpl(
) {
handleTag(secrets) { tagCommunicator ->
try {
tagCommunicator.sendCharacter(nfcCharacter)
if (nfcCharacter is VBNfcCharacter) {
Log.d("SendCharacter", "VBNfcCharacter")
val castNfcCharacter: VBNfcCharacter = nfcCharacter
tagCommunicator.sendCharacter(castNfcCharacter)
} else if (nfcCharacter is BENfcCharacter) {
Log.d("SendCharacter", "BENfcCharacter")
val castNfcCharacter: BENfcCharacter = nfcCharacter
tagCommunicator.sendCharacter(castNfcCharacter)
}
onComplete.invoke()
"Sent character successfully!"
} catch (e: Throwable) {
@@ -151,103 +156,20 @@ class ScanScreenControllerImpl(
componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
}
private fun addCharacterScannedIntoDatabase(nfcCharacter: NfcCharacter): String {
val application = componentActivity.applicationContext as VBHelper
val storageRepository = application.container.db
override fun characterFromNfc(nfcCharacter: NfcCharacter): String {
val nfcConverter = FromNfcConverter(
componentActivity = componentActivity
)
return nfcConverter.addCharacter(nfcCharacter)
}
val dimData = storageRepository
.dimDao()
.getDimById(nfcCharacter.dimId.toInt())
if (dimData == null) return "Card not found"
val cardCharData = storageRepository
.characterDao()
.getCharacterByMonIndex(nfcCharacter.charIndex.toInt(), dimData.id)
val characterData = UserCharacter(
charId = cardCharData.id,
stage = nfcCharacter.stage.toInt(),
attribute = nfcCharacter.attribute,
ageInDays = nfcCharacter.ageInDays.toInt(),
nextAdventureMissionStage = nfcCharacter.nextAdventureMissionStage.toInt(),
mood = nfcCharacter.mood.toInt(),
vitalPoints = nfcCharacter.vitalPoints.toInt(),
transformationCountdown = nfcCharacter.transformationCountdownInMinutes.toInt(),
injuryStatus = nfcCharacter.injuryStatus,
trophies = nfcCharacter.trophies.toInt(),
currentPhaseBattlesWon = nfcCharacter.currentPhaseBattlesWon.toInt(),
currentPhaseBattlesLost = nfcCharacter.currentPhaseBattlesLost.toInt(),
totalBattlesWon = nfcCharacter.totalBattlesWon.toInt(),
totalBattlesLost = nfcCharacter.totalBattlesLost.toInt(),
activityLevel = nfcCharacter.activityLevel.toInt(),
heartRateCurrent = nfcCharacter.heartRateCurrent.toInt(),
characterType = when (nfcCharacter) {
is BENfcCharacter -> DeviceType.BEDevice
else -> DeviceType.VBDevice
},
isActive = true
override suspend fun characterToNfc(characterId: Long): NfcCharacter {
val nfcGenerator = ToNfcConverter(
componentActivity = componentActivity
)
storageRepository
.userCharacterDao()
.clearActiveCharacter()
val characterId: Long = storageRepository
.userCharacterDao()
.insertCharacterData(characterData)
if (nfcCharacter is BENfcCharacter) {
val extraCharacterData = BECharacterData(
id = characterId,
trainingHp = nfcCharacter.trainingHp.toInt(),
trainingAp = nfcCharacter.trainingAp.toInt(),
trainingBp = nfcCharacter.trainingBp.toInt(),
remainingTrainingTimeInMinutes = nfcCharacter.remainingTrainingTimeInMinutes.toInt(),
itemEffectActivityLevelValue = nfcCharacter.itemEffectActivityLevelValue.toInt(),
itemEffectMentalStateValue = nfcCharacter.itemEffectMentalStateValue.toInt(),
itemEffectMentalStateMinutesRemaining = nfcCharacter.itemEffectMentalStateMinutesRemaining.toInt(),
itemEffectActivityLevelMinutesRemaining = nfcCharacter.itemEffectActivityLevelMinutesRemaining.toInt(),
itemEffectVitalPointsChangeValue = nfcCharacter.itemEffectVitalPointsChangeValue.toInt(),
itemEffectVitalPointsChangeMinutesRemaining = nfcCharacter.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
abilityRarity = nfcCharacter.abilityRarity,
abilityType = nfcCharacter.abilityType.toInt(),
abilityBranch = nfcCharacter.abilityBranch.toInt(),
abilityReset = nfcCharacter.abilityReset.toInt(),
rank = nfcCharacter.abilityReset.toInt(),
itemType = nfcCharacter.itemType.toInt(),
itemMultiplier = nfcCharacter.itemMultiplier.toInt(),
itemRemainingTime = nfcCharacter.itemRemainingTime.toInt(),
otp0 = "", //nfcCharacter.value!!.otp0.toString(),
otp1 = "", //nfcCharacter.value!!.otp1.toString(),
minorVersion = nfcCharacter.characterCreationFirmwareVersion.minorVersion.toInt(),
majorVersion = nfcCharacter.characterCreationFirmwareVersion.majorVersion.toInt(),
)
storageRepository
.userCharacterDao()
.insertBECharacterData(extraCharacterData)
val transformationHistoryWatch = nfcCharacter.transformationHistory
transformationHistoryWatch.map { item ->
if (item.toCharIndex.toInt() != 255) {
val date = GregorianCalendar(item.year.toInt(), item.month.toInt(), item.day.toInt())
.time
.time
storageRepository
.characterDao()
.insertTransformation(characterId, item.toCharIndex.toInt(), dimData.id, date)
storageRepository
.dexDao()
.insertCharacter(item.toCharIndex.toInt(), dimData.id, date)
}
}
} else if (nfcCharacter is VBNfcCharacter) {
return "Not implemented yet"
}
return "Done reading character!"
val character = nfcGenerator.characterToNfc(characterId)
Log.d("CharacterType", character.toString())
return character
}
}
@@ -0,0 +1,253 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.util.Log
import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory
import com.github.nacabaro.vbhelper.utils.DeviceType
import java.util.GregorianCalendar
class FromNfcConverter (
componentActivity: ComponentActivity
) {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
fun addCharacter(nfcCharacter: NfcCharacter): String {
val cardData = database
.cardDao()
.getDimById(nfcCharacter.dimId.toInt())
if (cardData == null)
return "Card not found"
val cardCharData = database
.characterDao()
.getCharacterByMonIndex(nfcCharacter.charIndex.toInt(), cardData.id)
updateCardProgress(nfcCharacter, cardData)
val characterData = UserCharacter(
charId = cardCharData.id,
ageInDays = nfcCharacter.ageInDays.toInt(),
mood = nfcCharacter.mood.toInt(),
vitalPoints = nfcCharacter.vitalPoints.toInt(),
transformationCountdown = nfcCharacter.transformationCountdownInMinutes.toInt(),
injuryStatus = nfcCharacter.injuryStatus,
trophies = nfcCharacter.trophies.toInt(),
currentPhaseBattlesWon = nfcCharacter.currentPhaseBattlesWon.toInt(),
currentPhaseBattlesLost = nfcCharacter.currentPhaseBattlesLost.toInt(),
totalBattlesWon = nfcCharacter.totalBattlesWon.toInt(),
totalBattlesLost = nfcCharacter.totalBattlesLost.toInt(),
activityLevel = nfcCharacter.activityLevel.toInt(),
heartRateCurrent = nfcCharacter.heartRateCurrent.toInt(),
characterType = when (nfcCharacter) {
is BENfcCharacter -> DeviceType.BEDevice
else -> DeviceType.VBDevice
},
isActive = true
)
database
.userCharacterDao()
.clearActiveCharacter()
val characterId: Long = database
.userCharacterDao()
.insertCharacterData(characterData)
if (nfcCharacter is BENfcCharacter) {
addBeCharacterToDatabase(
characterId = characterId,
nfcCharacter = nfcCharacter
)
} else if (nfcCharacter is VBNfcCharacter) {
addVbCharacterToDatabase(
characterId = characterId,
nfcCharacter = nfcCharacter
)
}
addTransformationHistoryToDatabase(
characterId = characterId,
nfcCharacter = nfcCharacter,
dimData = cardData
)
addVitalsHistoryToDatabase(
characterId = characterId,
nfcCharacter = nfcCharacter
)
return "Done reading character!"
}
private fun updateCardProgress(
nfcCharacter: NfcCharacter,
cardData: Card
) {
val currentCardProgress = CardProgress(
cardId = cardData.id,
currentStage = nfcCharacter.nextAdventureMissionStage.toInt(),
unlocked = nfcCharacter.nextAdventureMissionStage.toInt() > cardData.stageCount
)
database
.cardProgressDao()
.updateDimProgress(currentCardProgress)
}
private fun addVbCharacterToDatabase(
characterId: Long,
nfcCharacter: VBNfcCharacter
) {
val extraCharacterData = VBCharacterData(
id = characterId,
generation = nfcCharacter.generation.toInt(),
totalTrophies = nfcCharacter.totalTrophies.toInt()
)
database
.userCharacterDao()
.insertVBCharacterData(extraCharacterData)
addSpecialMissionsToDatabase(nfcCharacter, characterId)
}
private fun addSpecialMissionsToDatabase(
nfcCharacter: VBNfcCharacter,
characterId: Long
) {
val specialMissionsWatch = nfcCharacter.specialMissions
val specialMissionsDb = specialMissionsWatch.map { item ->
SpecialMissions(
characterId = characterId,
goal = item.goal.toInt(),
watchId = item.id.toInt(),
progress = item.progress.toInt(),
status = item.status,
timeElapsedInMinutes = item.timeElapsedInMinutes.toInt(),
timeLimitInMinutes = item.timeLimitInMinutes.toInt(),
missionType = item.type,
)
}
database
.userCharacterDao()
.insertSpecialMissions(*specialMissionsDb.toTypedArray())
}
private fun addBeCharacterToDatabase(
characterId: Long,
nfcCharacter: BENfcCharacter
) {
val extraCharacterData = BECharacterData(
id = characterId,
trainingHp = nfcCharacter.trainingHp.toInt(),
trainingAp = nfcCharacter.trainingAp.toInt(),
trainingBp = nfcCharacter.trainingBp.toInt(),
remainingTrainingTimeInMinutes = nfcCharacter.remainingTrainingTimeInMinutes.toInt(),
itemEffectActivityLevelValue = nfcCharacter.itemEffectActivityLevelValue.toInt(),
itemEffectMentalStateValue = nfcCharacter.itemEffectMentalStateValue.toInt(),
itemEffectMentalStateMinutesRemaining = nfcCharacter.itemEffectMentalStateMinutesRemaining.toInt(),
itemEffectActivityLevelMinutesRemaining = nfcCharacter.itemEffectActivityLevelMinutesRemaining.toInt(),
itemEffectVitalPointsChangeValue = nfcCharacter.itemEffectVitalPointsChangeValue.toInt(),
itemEffectVitalPointsChangeMinutesRemaining = nfcCharacter.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
abilityRarity = nfcCharacter.abilityRarity,
abilityType = nfcCharacter.abilityType.toInt(),
abilityBranch = nfcCharacter.abilityBranch.toInt(),
abilityReset = nfcCharacter.abilityReset.toInt(),
rank = nfcCharacter.abilityReset.toInt(),
itemType = nfcCharacter.itemType.toInt(),
itemMultiplier = nfcCharacter.itemMultiplier.toInt(),
itemRemainingTime = nfcCharacter.itemRemainingTime.toInt(),
otp0 = "", //nfcCharacter.value!!.otp0.toString(),
otp1 = "", //nfcCharacter.value!!.otp1.toString(),
minorVersion = nfcCharacter.characterCreationFirmwareVersion.minorVersion.toInt(),
majorVersion = nfcCharacter.characterCreationFirmwareVersion.majorVersion.toInt(),
)
database
.userCharacterDao()
.insertBECharacterData(extraCharacterData)
}
private fun addVitalsHistoryToDatabase(
characterId: Long,
nfcCharacter: NfcCharacter
) {
val vitalsHistoryWatch = nfcCharacter.vitalHistory
val vitalsHistory = vitalsHistoryWatch.map { historyElement ->
Log.d("VitalsHistory", "${historyElement.year.toInt()} ${historyElement.month.toInt()} ${historyElement.day.toInt()}")
VitalsHistory(
charId = characterId,
year = historyElement.year.toInt(),
month = historyElement.month.toInt(),
day = historyElement.day.toInt(),
vitalPoints = historyElement.vitalsGained.toInt()
)
}
database
.userCharacterDao()
.insertVitals(*vitalsHistory.toTypedArray())
}
private fun addTransformationHistoryToDatabase(
characterId: Long,
nfcCharacter: NfcCharacter,
dimData: Card
) {
val transformationHistoryWatch = nfcCharacter.transformationHistory
transformationHistoryWatch.map { item ->
if (item.toCharIndex.toInt() != 255) {
val date = GregorianCalendar(
item.year.toInt(),
item.month.toInt(),
item.day.toInt()
)
.time
.time
database
.userCharacterDao()
.insertTransformation(
characterId,
item.toCharIndex.toInt(),
dimData.id,
date
)
database
.dexDao()
.insertCharacter(
item.toCharIndex.toInt(),
dimData.id,
date
)
}
}
}
}
@@ -0,0 +1,273 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.icu.util.Calendar
import android.util.Log
import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.be.FirmwareVersion
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.utils.DeviceType
import java.util.Date
class ToNfcConverter(
private val componentActivity: ComponentActivity
) {
private val application: VBHelper = componentActivity.applicationContext as VBHelper
private val database: AppDatabase = application.container.db
suspend fun characterToNfc(
characterId: Long
): NfcCharacter {
val app = componentActivity.applicationContext as VBHelper
val database = app.container.db
val userCharacter = database
.userCharacterDao()
.getCharacter(characterId)
val characterInfo = database
.characterDao()
.getCharacterInfo(userCharacter.charId)
val currentCardStage = database
.cardProgressDao()
.getCardProgress(characterInfo.cardId)
return if (userCharacter.characterType == DeviceType.BEDevice)
nfcToBENfc(characterId, characterInfo, currentCardStage, userCharacter)
else
nfcToVBNfc(characterId, characterInfo, currentCardStage, userCharacter)
}
private suspend fun nfcToVBNfc(
characterId: Long,
characterInfo: CharacterDtos.CardCharacterInfo,
currentCardStage: Int,
userCharacter: UserCharacter
): VBNfcCharacter {
val vbData = database
.userCharacterDao()
.getVbData(characterId)
val paddedTransformationArray = generateTransformationHistory(characterId, 9)
val watchSpecialMissions = generateSpecialMissionsArray(characterId)
val nfcData = VBNfcCharacter(
dimId = characterInfo.cardId.toUShort(),
charIndex = characterInfo.charId.toUShort(),
stage = characterInfo.stage.toByte(),
attribute = characterInfo.attribute,
ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = currentCardStage.toByte(),
mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(),
transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(),
injuryStatus = userCharacter.injuryStatus,
trophies = userCharacter.trophies.toUShort(),
currentPhaseBattlesWon = userCharacter.currentPhaseBattlesWon.toUShort(),
currentPhaseBattlesLost = userCharacter.currentPhaseBattlesLost.toUShort(),
totalBattlesWon = userCharacter.totalBattlesWon.toUShort(),
totalBattlesLost = userCharacter.totalBattlesLost.toUShort(),
activityLevel = userCharacter.activityLevel.toByte(),
heartRateCurrent = userCharacter.heartRateCurrent.toUByte(),
transformationHistory = paddedTransformationArray,
vitalHistory = generateVitalsHistoryArray(characterId),
appReserved1 = ByteArray(12) {0},
appReserved2 = Array(3) {0u},
generation = vbData.generation.toUShort(),
totalTrophies = vbData.totalTrophies.toUShort(),
specialMissions = watchSpecialMissions.toTypedArray()
)
return nfcData
}
private suspend fun generateSpecialMissionsArray(
characterId: Long
): List<SpecialMission> {
val specialMissions = database
.userCharacterDao()
.getSpecialMissions(characterId)
val watchSpecialMissions = specialMissions.map {
SpecialMission(
goal = it.goal.toUShort(),
id = it.watchId.toUShort(),
progress = it.progress.toUShort(),
status = it.status,
timeElapsedInMinutes = it.timeElapsedInMinutes.toUShort(),
timeLimitInMinutes = it.timeLimitInMinutes.toUShort(),
type = it.missionType
)
}
return watchSpecialMissions
}
private suspend fun generateVitalsHistoryArray(
characterId: Long
): Array<NfcCharacter.DailyVitals> {
val vitalsHistory = database
.userCharacterDao()
.getVitalsHistory(characterId)
val nfcVitalsHistory = Array(7) {
NfcCharacter.DailyVitals(0u, 0u, 0u, 0u)
}
vitalsHistory.mapIndexed { index, historyElement ->
var actualYear = 0
if (historyElement.year != 2000) {
actualYear = historyElement.year
}
nfcVitalsHistory[index] = NfcCharacter.DailyVitals(
day = historyElement.day.toUByte(),
month = historyElement.month.toUByte(),
year = actualYear.toUShort(),
vitalsGained = vitalsHistory[index].vitalPoints.toUShort()
)
}
nfcVitalsHistory.map {
Log.d("NFC", it.toString())
}
return nfcVitalsHistory
}
private suspend fun nfcToBENfc(
characterId: Long,
characterInfo: CharacterDtos.CardCharacterInfo,
currentCardStage: Int,
userCharacter: UserCharacter
): BENfcCharacter {
val beData = database
.userCharacterDao()
.getBeData(characterId)
val paddedTransformationArray = generateTransformationHistory(characterId)
val nfcData = BENfcCharacter(
dimId = characterInfo.cardId.toUShort(),
charIndex = characterInfo.charId.toUShort(),
stage = characterInfo.stage.toByte(),
attribute = characterInfo.attribute,
ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = currentCardStage.toByte(),
mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(),
itemEffectMentalStateValue = beData.itemEffectMentalStateValue.toByte(),
itemEffectMentalStateMinutesRemaining = beData.itemEffectMentalStateMinutesRemaining.toByte(),
itemEffectActivityLevelValue = beData.itemEffectActivityLevelValue.toByte(),
itemEffectActivityLevelMinutesRemaining = beData.itemEffectActivityLevelMinutesRemaining.toByte(),
itemEffectVitalPointsChangeValue = beData.itemEffectVitalPointsChangeValue.toByte(),
itemEffectVitalPointsChangeMinutesRemaining = beData.itemEffectVitalPointsChangeMinutesRemaining.toByte(),
transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(),
injuryStatus = userCharacter.injuryStatus,
trainingPp = userCharacter.trophies.toUShort(),
currentPhaseBattlesWon = userCharacter.currentPhaseBattlesWon.toUShort(),
currentPhaseBattlesLost = userCharacter.currentPhaseBattlesLost.toUShort(),
totalBattlesWon = userCharacter.totalBattlesWon.toUShort(),
totalBattlesLost = userCharacter.totalBattlesLost.toUShort(),
activityLevel = userCharacter.activityLevel.toByte(),
heartRateCurrent = userCharacter.heartRateCurrent.toUByte(),
transformationHistory = paddedTransformationArray,
vitalHistory = generateVitalsHistoryArray(characterId),
appReserved1 = ByteArray(12) {0},
appReserved2 = Array(3) {0u},
trainingHp = beData.trainingHp.toUShort(),
trainingAp = beData.trainingAp.toUShort(),
trainingBp = beData.trainingBp.toUShort(),
remainingTrainingTimeInMinutes = beData.remainingTrainingTimeInMinutes.toUShort(),
abilityRarity = beData.abilityRarity,
abilityType = beData.abilityType.toUShort(),
abilityBranch = beData.abilityBranch.toUShort(),
abilityReset = beData.abilityReset.toByte(),
rank = beData.rank.toByte(),
itemType = beData.itemType.toByte(),
itemMultiplier = beData.itemMultiplier.toByte(),
itemRemainingTime = beData.itemRemainingTime.toByte(),
otp0 = byteArrayOf(8),
otp1 = byteArrayOf(8),
characterCreationFirmwareVersion = FirmwareVersion(
minorVersion = beData.minorVersion.toByte(),
majorVersion = beData.majorVersion.toByte()
)
)
return nfcData
}
private suspend fun generateTransformationHistory(
characterId: Long,
length: Int = 8
): Array<NfcCharacter.Transformation> {
val transformationHistory = database
.userCharacterDao()
.getTransformationHistory(characterId)!!
.map {
val date = Date(it.transformationDate)
val calendar = android.icu.util.GregorianCalendar()
calendar.time = date
NfcCharacter.Transformation(
toCharIndex = it.monIndex.toUByte(),
year = calendar
.get(Calendar.YEAR)
.toUShort(),
month = calendar
.get(Calendar.MONTH)
.toUByte(),
day = calendar
.get(Calendar.DAY_OF_MONTH)
.toUByte()
)
}.toTypedArray()
val paddedTransformationArray = padTransformationArray(transformationHistory, length)
return paddedTransformationArray
}
private fun padTransformationArray(
transformationArray: Array<NfcCharacter.Transformation>,
length: Int
): Array<NfcCharacter.Transformation> {
if (transformationArray.size >= 8) {
return transformationArray
}
val paddedArray = Array(length) {
NfcCharacter.Transformation(
toCharIndex = 255u,
year = 65535u,
month = 255u,
day = 255u
)
}
System.arraycopy(transformationArray, 0, paddedArray, 0, transformationArray.size)
return paddedArray
}
}
@@ -0,0 +1,40 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
@Composable
fun CreditsScreen(
navController: NavController
) {
Scaffold (
topBar = {
TopBanner(
text = "Credits",
onBackClick = {
navController.popBackStack()
}
)
},
modifier = Modifier
.fillMaxSize()
) { contentPadding ->
Column (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
SettingsSection("Reverse engineering")
SettingsEntry(title = "cyanic", description = "Reversed the firmware and helped us during development.") { }
SettingsSection("Application development")
SettingsEntry(title = "cfogrady", description = "Developed vb-lib-nfc and part of this application.") { }
SettingsEntry(title = "nacabaro", description = "Developed this application.") { }
SettingsEntry(title = "lightheel", description = "Developing the battling part for this application, including server. Still in the works.") { }
}
}
}
@@ -18,6 +18,7 @@ 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.navigation.NavigationItems
@Composable
fun SettingsScreen(
@@ -46,6 +47,16 @@ fun SettingsScreen(
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
settingsScreenController.onClickImportApk()
}
SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import card", description = "Import DiM/BEm card file") {
settingsScreenController.onClickImportCard()
}
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
SettingsSection("About and credits")
SettingsEntry(title = "Credits", description = "Credits") {
navController.navigate(NavigationItems.Credits.route)
}
SettingsEntry(title = "About", description = "About") { }
SettingsSection("Data management")
SettingsEntry(title = "Export data", description = "Export application database") {
settingsScreenController.onClickOpenDirectory()
@@ -53,14 +64,6 @@ fun SettingsScreen(
SettingsEntry(title = "Import data", description = "Import application database") {
settingsScreenController.onClickImportDatabase()
}
SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file") {
settingsScreenController.onClickImportCard()
}
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
SettingsSection("About and credits")
SettingsEntry(title = "Credits", description = "Credits") { }
SettingsEntry(title = "About", description = "About") { }
}
}
}
@@ -10,11 +10,14 @@ import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import com.github.cfogrady.vb.dim.card.BemCard
import com.github.cfogrady.vb.dim.card.DimCard
import com.github.cfogrady.vb.dim.card.DimReader
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.domain.card.Card
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.SecretsImporter
@@ -115,9 +118,9 @@ class SettingsScreenControllerImpl(
val card = dimReader.readCard(fileReader, false)
val cardModel = Card(
dimId = card.header.dimId,
cardId = card.header.dimId,
logo = card.spriteData.sprites[0].pixelData,
name = card.spriteData.text, // TODO Make user write card name// TODO Make user write card name
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,
@@ -125,35 +128,87 @@ class SettingsScreenControllerImpl(
)
val dimId = database
.dimDao()
.cardDao()
.insertNewDim(cardModel)
val characters = card.characterStats.characterEntries
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 -> 55
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 = characters[index].attribute,
attribute = NfcCharacter.Attribute.entries[characters[index].attribute],
baseHp = characters[index].hp,
baseBp = characters[index].dp,
baseAp = characters[index].ap,
sprite1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
sprite2 = card.spriteData.sprites[spriteCounter + 2].pixelData,
nameWidth = card.spriteData.sprites[spriteCounter].width,
nameHeight = card.spriteData.sprites[spriteCounter].height,
spritesWidth = card.spriteData.sprites[spriteCounter + 1].width,
spritesHeight = card.spriteData.sprites[spriteCounter + 1].height
nameWidth = card.spriteData.sprites[spriteCounter].spriteDimensions.width,
nameHeight = card.spriteData.sprites[spriteCounter].spriteDimensions.height,
)
)
@@ -171,18 +226,6 @@ class SettingsScreenControllerImpl(
database
.characterDao()
.insertCharacter(*domainCharacters.toTypedArray())
val sprites = card.spriteData.sprites.map { sprite ->
Sprites(
id = 0,
sprite = sprite.pixelData,
width = sprite.width,
height = sprite.height
)
}
database
.characterDao()
.insertSprite(*sprites.toTypedArray())
}
inputStream?.close()
@@ -1,55 +1,38 @@
package com.github.nacabaro.vbhelper.screens
package com.github.nacabaro.vbhelper.screens.spriteViewer
import android.graphics.Bitmap
import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
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.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.source.SpriteRepo
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
@Composable
fun SpriteViewer(
navController: NavController
navController: NavController,
spriteViewerController: SpriteViewerController
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val db = application.container.db
val spriteRepo = SpriteRepo(db)
val spriteList = remember { mutableStateListOf<Sprites>() }
val spriteList = remember { mutableStateListOf<Bitmap>() }
Log.d("SpriteViewer", "spriteList: $spriteList")
LaunchedEffect(spriteRepo) {
coroutineScope.launch {
spriteList.clear()
spriteList.addAll(spriteRepo.getAllSprites())
}
LaunchedEffect(spriteViewerController) {
val sprites = spriteViewerController.getAllSprites()
val bitmapData = spriteViewerController.convertToBitmap(sprites)
spriteList.addAll(bitmapData)
}
Scaffold (
@@ -69,13 +52,7 @@ fun SpriteViewer(
.padding(top = contentPadding.calculateTopPadding())
) {
items(spriteList) { sprite ->
val bitmap = remember (sprite.sprite) {
Log.d("SpriteViewer", "sprite: $sprite")
Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.sprite))
}
}
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val imageBitmap = remember(sprite) { sprite.asImageBitmap() }
Image(
bitmap = imageBitmap,
contentDescription = "Sprite",
@@ -0,0 +1,9 @@
package com.github.nacabaro.vbhelper.screens.spriteViewer
import android.graphics.Bitmap
import com.github.nacabaro.vbhelper.domain.characters.Sprite
interface SpriteViewerController {
suspend fun getAllSprites(): List<Sprite>
fun convertToBitmap(sprites: List<Sprite>): List<Bitmap>
}
@@ -0,0 +1,68 @@
package com.github.nacabaro.vbhelper.screens.spriteViewer
import android.graphics.Bitmap
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.asImageBitmap
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import java.nio.ByteBuffer
class SpriteViewerControllerImpl(
private val context: ComponentActivity
) : SpriteViewerController {
override suspend fun getAllSprites(): List<Sprite> {
val applicationContext = context.applicationContext as VBHelper
val db = applicationContext.container.db
val sprites = db.spriteDao().getAllSprites()
return sprites
}
// I don't like this, chief
override fun convertToBitmap(sprites: List<Sprite>): List<Bitmap> {
val bitmapList = mutableListOf<Bitmap>()
for (sprite in sprites) {
Log.d("SpriteViewer", "sprite: $sprite")
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle1))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle2))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk1))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk2))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun1))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun2))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain1))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain2))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteHappy))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteSleep))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteAttack))
})
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteDodge))
})
}
return bitmapList
}
}
@@ -0,0 +1,117 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
fun getAdventureTime(time: Int): String {
return when (time) {
360 -> "6 hours"
720 -> "12 hours"
1440 -> "24 hours"
else -> "Unknown"
}
}
@Composable
fun StorageAdventureTimeDialog(
onClickSendToAdventure: (time: Long) -> Unit,
onDismissRequest: () -> Unit
) {
val times = arrayOf(360, 720, 1440)
var expanded by remember { mutableStateOf(false) }
var itemPosition by remember { mutableIntStateOf(-1) }
Dialog(
onDismissRequest = onDismissRequest
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Box(
modifier = Modifier
.padding(16.dp)
) {
Row (
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.width(256.dp)
.clickable(true) {
expanded = true
}
) {
Text(
text = when (itemPosition) {
-1 -> "Choose time"
else -> getAdventureTime(times[itemPosition])
}
)
Icon(
painter = painterResource(R.drawable.baseline_single_arrow_down),
contentDescription = "Show more"
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(256.dp)
) {
times.forEach { time ->
DropdownMenuItem(
text = { Text(getAdventureTime(time)) },
onClick = {
itemPosition = times.indexOf(time)
expanded = false
}
)
}
}
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = {
if (itemPosition != -1) {
onClickSendToAdventure(times[itemPosition].toLong())
onDismissRequest()
}
}
) {
Text(text = "Send on adventure")
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = onDismissRequest
) {
Text(text = "Dismiss")
}
}
}
}
}
@@ -0,0 +1,163 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
import kotlinx.coroutines.launch
@Composable
fun StorageDialog(
characterId: Long,
onDismissRequest: () -> Unit,
onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit,
onClickSendToAdventure: (time: Long) -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val character = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
val characterSprite = remember { mutableStateOf<BitmapData?>(null) }
val characterName = remember { mutableStateOf<BitmapData?>(null) }
var onSendToAdventureClicked by remember { mutableStateOf(false) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
character.value = storageRepository.getSingleCharacter(characterId)
characterSprite.value = BitmapData(
bitmap = character.value!!.spriteIdle,
width = character.value!!.spriteWidth,
height = character.value!!.spriteHeight
)
characterName.value = BitmapData(
bitmap = character.value!!.nameSprite,
width = character.value!!.nameSpriteWidth,
height = character.value!!.nameSpriteHeight
)
}
}
Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
Card(
shape = RoundedCornerShape(16.dp)
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
if (character.value != null &&
characterSprite.value != null &&
characterName.value != null
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
val bitmap = remember (characterSprite.value!!) { characterSprite.value!!.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (characterSprite.value!!.width * 4 / density).dp
Image(
bitmap = imageBitmap,
contentDescription = "Character image",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
)
val nameBitmap = remember (characterName.value!!) { characterName.value!!.getBitmap() }
val nameImageBitmap = remember(nameBitmap) { nameBitmap.asImageBitmap() }
val nameDpSize = (characterName.value!!.width * 4 / density).dp
Image(
bitmap = nameImageBitmap,
contentDescription = "Character image",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(nameDpSize)
)
}
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
) {
Button(
onClick = onSendToBracelet,
modifier = Modifier
.weight(1f)
) {
Text(text = "Send to watch")
}
Spacer(
modifier = Modifier
.padding(4.dp)
)
Button(
onClick = onClickSetActive,
) {
Text(text = "Set active")
}
}
Button(
onClick = {
onSendToAdventureClicked = true
},
modifier = Modifier
.fillMaxWidth()
) {
Text(text = "Send on adventure")
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = onDismissRequest
) {
Text(text = "Close")
}
}
}
}
if (onSendToAdventureClicked) {
StorageAdventureTimeDialog(
onClickSendToAdventure = { time ->
onClickSendToAdventure(time)
},
onDismissRequest = { onSendToAdventureClicked = false }
)
}
}
@@ -0,0 +1,145 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
import android.widget.Toast
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.launch
@Composable
fun StorageScreen(
navController: NavController,
storageScreenController: StorageScreenControllerImpl,
adventureScreenController: AdventureScreenControllerImpl
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) }
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
LaunchedEffect(storageRepository, selectedCharacter) {
coroutineScope.launch {
val characterList = storageRepository.getAllCharacters()
monList.value = characterList
}
}
Scaffold (
topBar = {
TopBanner(
text = "My characters",
onAdventureClick = {
navController.navigate(NavigationItems.Adventure.route)
}
)
}
) { contentPadding ->
if (monList.value.isEmpty()) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(
text = "Nothing to see here",
textAlign = TextAlign.Center,
modifier = Modifier
)
}
} else {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding())
) {
items(monList.value) { index ->
CharacterEntry(
icon = BitmapData(
bitmap = index.spriteIdle,
width = index.spriteWidth,
height = index.spriteHeight
),
onClick = {
if (!index.isInAdventure) {
selectedCharacter = index.id
} else {
Toast.makeText(
application,
"This character is in an adventure",
Toast.LENGTH_SHORT
).show()
navController.navigate(
NavigationItems.Adventure.route
)
}
},
)
}
}
}
if (selectedCharacter != null) {
StorageDialog(
characterId = selectedCharacter!!,
onDismissRequest = { selectedCharacter = null },
onClickSetActive = {
storageScreenController
.setActive(selectedCharacter!!) {
selectedCharacter = null
navController.navigate(NavigationItems.Home.route)
}
},
onSendToBracelet = {
navController.navigate(
NavigationItems.Scan.route.replace(
"{characterId}",
selectedCharacter.toString()
)
)
},
onClickSendToAdventure = { time ->
adventureScreenController
.sendCharacterToAdventure(
characterId = selectedCharacter!!,
timeInMinutes = time
)
selectedCharacter = null
}
)
}
}
}
@@ -0,0 +1,5 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
interface StorageScreenController {
fun setActive(characterId: Long, onCompletion: () -> Unit)
}
@@ -0,0 +1,31 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class StorageScreenControllerImpl(
private val componentActivity: ComponentActivity
): StorageScreenController {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
override fun setActive(characterId: Long, onCompletion: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database.userCharacterDao().clearActiveCharacter()
database.userCharacterDao().setActiveCharacter(characterId)
componentActivity.runOnUiThread {
Toast.makeText(
componentActivity,
"Active character updated!",
Toast.LENGTH_SHORT
).show()
onCompletion()
}
}
}
}
@@ -1,7 +1,6 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@@ -1,12 +0,0 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.Sprites
class SpriteRepo (
private val db: AppDatabase
) {
suspend fun getAllSprites(): List<Sprites> {
return db.characterDao().getAllSprites()
}
}
@@ -2,9 +2,10 @@ package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
class StorageRepository (
private val db: AppDatabase
@@ -13,20 +14,28 @@ class StorageRepository (
return db.userCharacterDao().getAllCharacters()
}
suspend fun getSingleCharacter(id: Long): UserCharacter {
return db.userCharacterDao().getCharacter(id)
suspend fun getSingleCharacter(id: Long): CharacterDtos.CharacterWithSprites {
return db.userCharacterDao().getCharacterWithSprites(id)
}
suspend fun getCharacterBeData(id: Long): BECharacterData {
return db.userCharacterDao().getBeData(id)
}
fun getTransformationHistory(characterId: Long): List<CharacterDtos.TransformationHistory>? {
suspend fun getTransformationHistory(characterId: Long): List<CharacterDtos.TransformationHistory>? {
return db.userCharacterDao().getTransformationHistory(characterId)
}
suspend fun getCharacterData(id: Long): CharacterDtos.DiMInfo {
return db.characterDao().getCharacterInfo(id)
suspend fun getCharacterVbData(id: Long): VBCharacterData {
return db.userCharacterDao().getVbData(id)
}
suspend fun getSpecialMissions(id: Long): List<SpecialMissions> {
return db.userCharacterDao().getSpecialMissions(id)
}
suspend fun getItem(id: Long): ItemDtos.ItemsWithQuantities {
return db.itemDao().getItem(id)
}
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? {
@@ -37,8 +46,15 @@ class StorageRepository (
return db.userCharacterDao().deleteCharacterById(id)
}
fun setActiveCharacter(id: Long) {
db.userCharacterDao().clearActiveCharacter()
return db.userCharacterDao().setActiveCharacter(id)
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> {
return db.adventureDao().getAdventureCharacters()
}
suspend fun getBEBEmCharacters(): List<CharacterDtos.CharacterWithSprites> {
return db.userCharacterDao().getBEBemCharacters()
}
suspend fun getVBCharacters(): List<CharacterDtos.CharacterWithSprites> {
return db.userCharacterDao().getVBDimCharacters()
}
}
@@ -1,99 +0,0 @@
package com.github.nacabaro.vbhelper.utils
import android.content.Context
import android.icu.util.Calendar
import android.icu.util.GregorianCalendar
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.be.FirmwareVersion
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.source.StorageRepository
import java.util.Date
suspend fun characterToNfc(context: Context, characterId: Long): NfcCharacter? {
val app = context.applicationContext as VBHelper
val database = app.container.db
val storageRepository = StorageRepository(database)
val userCharacter = storageRepository.getSingleCharacter(characterId)
val characterInfo = storageRepository.getCharacterData(characterId)
if (userCharacter.characterType == DeviceType.BEDevice) {
val beData = storageRepository.getCharacterBeData(characterId)
val transformationHistory = storageRepository
.getTransformationHistory(characterId)!!
.map {
val date = Date(it.transformationDate)
val calendar = GregorianCalendar()
calendar.time = date
NfcCharacter.Transformation(
toCharIndex = it.monIndex.toUByte(),
year = calendar
.get(Calendar.YEAR)
.toUShort(),
month = calendar
.get(Calendar.MONTH)
.toUByte(),
day = calendar
.get(Calendar.DAY_OF_MONTH)
.toUByte()
)
}.toTypedArray()
val paddedTransformationArray = padTransformationArray(transformationHistory)
val nfcData = BENfcCharacter(
dimId = characterInfo.cardId.toUShort(),
charIndex = characterInfo.charId.toUShort(),
stage = userCharacter.stage.toByte(),
attribute = userCharacter.attribute,
ageInDays = userCharacter.ageInDays.toByte(),
nextAdventureMissionStage = userCharacter.nextAdventureMissionStage.toByte(),
mood = userCharacter.mood.toByte(),
vitalPoints = userCharacter.vitalPoints.toUShort(),
itemEffectMentalStateValue = beData.itemEffectMentalStateValue.toByte(),
itemEffectMentalStateMinutesRemaining = beData.itemEffectMentalStateMinutesRemaining.toByte(),
itemEffectActivityLevelValue = beData.itemEffectActivityLevelValue.toByte(),
itemEffectActivityLevelMinutesRemaining = beData.itemEffectActivityLevelMinutesRemaining.toByte(),
itemEffectVitalPointsChangeValue = beData.itemEffectVitalPointsChangeValue.toByte(),
itemEffectVitalPointsChangeMinutesRemaining = beData.itemEffectVitalPointsChangeMinutesRemaining.toByte(),
transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(),
injuryStatus = userCharacter.injuryStatus,
trainingPp = userCharacter.trophies.toUShort(),
currentPhaseBattlesWon = userCharacter.currentPhaseBattlesWon.toUShort(),
currentPhaseBattlesLost = userCharacter.currentPhaseBattlesLost.toUShort(),
totalBattlesWon = userCharacter.totalBattlesWon.toUShort(),
totalBattlesLost = userCharacter.totalBattlesLost.toUShort(),
activityLevel = userCharacter.activityLevel.toByte(),
heartRateCurrent = userCharacter.heartRateCurrent.toUByte(),
transformationHistory = paddedTransformationArray,
vitalHistory = Array(7) {
NfcCharacter.DailyVitals(0u, 0u, 0u, 0u)
},
appReserved1 = ByteArray(12) {0},
appReserved2 = Array(3) {0u},
trainingHp = beData.trainingHp.toUShort(),
trainingAp = beData.trainingAp.toUShort(),
trainingBp = beData.trainingBp.toUShort(),
remainingTrainingTimeInMinutes = beData.remainingTrainingTimeInMinutes.toUShort(),
abilityRarity = beData.abilityRarity,
abilityType = beData.abilityType.toUShort(),
abilityBranch = beData.abilityBranch.toUShort(),
abilityReset = beData.abilityReset.toByte(),
rank = beData.rank.toByte(),
itemType = beData.itemType.toByte(),
itemMultiplier = beData.itemMultiplier.toByte(),
itemRemainingTime = beData.itemRemainingTime.toByte(),
otp0 = byteArrayOf(8),
otp1 = byteArrayOf(8),
characterCreationFirmwareVersion = FirmwareVersion(
minorVersion = beData.minorVersion.toByte(),
majorVersion = beData.majorVersion.toByte()
)
)
return nfcData
}
return null
}
@@ -1,23 +0,0 @@
package com.github.nacabaro.vbhelper.utils
import com.github.cfogrady.vbnfc.data.NfcCharacter
fun padTransformationArray(
transformationArray: Array<NfcCharacter.Transformation>
): Array<NfcCharacter.Transformation> {
if (transformationArray.size >= 8) {
return transformationArray
}
val paddedArray = Array(8) {
NfcCharacter.Transformation(
toCharIndex = 255u,
year = 65535u,
month = 255u,
day = 255u
)
}
System.arraycopy(transformationArray, 0, paddedArray, 0, transformationArray.size)
return paddedArray
}
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M40,840v-160l80,-80v-240l-80,-80v-160h80v80h80v-80h80v80h80v-80h80v160l-80,80v40h240v-40l-80,-80v-160h80v80h80v-80h80v80h80v-80h80v160l-80,80v240l80,80v160L560,840v-120q0,-33 -23.5,-56.5T480,640q-33,0 -56.5,23.5T400,720v120L40,840ZM120,760h200v-40q0,-66 47,-113t113,-47q66,0 113,47t47,113v40h200v-47l-80,-80v-306l47,-47L633,280l47,47v153L280,480v-153l47,-47L153,280l47,47v306l-80,80v47ZM480,520Z"
android:fillColor="#000000"/>
</vector>