Compare commits

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

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

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

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

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

- Things I might do
  - Move out of the SettingsScreenControllerImpl.kt file all the card import stuff, since it's slowly growing a lot, and it could benefit from some independence
2025-09-05 02:08:44 +02:00
Nacho 2586190e5c Update libraries 2025-09-02 18:01:11 +02:00
Nacho 1a6753d2aa Fixed date issues with TransformationHistory 2025-09-02 04:54:08 +02:00
Nacho f8ec899335 Version up and credits 2025-08-09 12:59:01 +02:00
Nacho b95edfa2a1 New icon 2025-08-09 12:55:38 +02:00
Nacho 1a570170ca Merge remote-tracking branch 'origin/main' 2025-08-06 20:18:43 +02:00
Nacho 1566876106 Version up 2025-08-06 20:18:32 +02:00
nacabaroandGitHub 019f07d827 Merge pull request #36 from nacabaro/card/card_management
Few things here and there
2025-08-06 20:13:09 +02:00
Nacho 4474e6689d Few things here and there
- Card management, you can now modify the name of the cards and remove cards too.
- Also, support for multiple cards with the same ID works too. When scanning for the first time, if a repeat card exists, the user will be asked to choose which card the character comes from. Future scans will not ask since it is stored in the watch app reserved area.
2025-08-06 20:10:21 +02:00
Nacho fb09004d13 Version up, again 2025-08-06 01:32:10 +02:00
Nacho dd1a62d184 Made a mistake
Accidentally locked BE watch items to only BEm characters. I have modified it to also include DiM characters in the BE.
2025-08-06 01:31:18 +02:00
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
Nacho 5e92895656 Made item icons a bit smaller in order to be similarly sized to character icons 2025-01-21 13:34:49 +01:00
Nacho 25500e9cb4 Made character icons bigger and also made the obscured characters follow material guidelines, as being pure black can be difficult to see in dark theme 2025-01-21 13:28:13 +01:00
nacabaroandGitHub 1a38cefb92 Merge pull request #23 from nacabaro/cleanup
Cleanup MainActivity
2025-01-21 13:12:38 +01:00
Nacho a974bd366e Moved read character from watch to its corresponding controller 2025-01-21 13:02:45 +01:00
Nacho bb5f66d167 Moved import card logic outside of the MainActivity 2025-01-21 12:42:52 +01:00
nacabaroandGitHub 09394871e5 Merge pull request #22 from nacabaro/database/items
Add items functionality
2025-01-20 15:49:22 +01:00
144 changed files with 7359 additions and 1658 deletions
+66 -35
View File
@@ -1,38 +1,69 @@
# VBHelper
## Developer Setup
Application to interact with the Vital series, VB, VH, VBC and VBBE.
1. Clone vb-nfc-reader (https://github.com/cfogrady/lib-vb-nfc)
2. Run vb-nfc-reader/publishToMavenLocal gradle task in the lib-vb-nfc project.
3. Clone vb-dim-reader (https://github.com/cfogrady/vb-dim-reader)
4. Run publishToMavenLocal gradle task in the vb-dim-reader project.
5. Create res/values/keys.xml within the app module.
6. Populate with:
```
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="password1">beHmacKey1</string>
<string name="password2">beHmacKey2</string>
<string name="decryptionKey">aesKey</string>
<integer-array name="substitutionArray">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
<item>14</item>
<item>15</item>
</integer-array>
</resources>
```
7. Replace the values in the keys.xml file with those extracted from the original app.
8. Run
## Current state of the project
Right now the project is still under development, and until further notice, any database updates will result in having to erase application data.
This document will be updated once the application does not need any more database resets.
## Features
As of now, the project allows you to read characters, view characters stats, and send them back to your watch.
You can also apply items to the characters read, such as special missions, or change timers, and store characters in the storage section.
You also earn new items every time an item such as a special mission (VB only) or a character completes an in-app adventure mission.
App also comes with a dex that will update every time a new character is added, and allows you to see evolution requirements and current adventure stage in the watch.
## How to set up
1. Download the latest version for VB Arena APK from a trustworthy source. If your download is a standalone APK, continue to step 2. Otherwise, if your download is an XAPK, do the following:
1. Using your phone file manager, rename the XAPK file to ZIP, and extract its contents. You can also do this with any other device, such as Windows, macOS and Linux.
2. Once the files are extracted, look for an APK called `com.bandai.vitalbraceletarena.apk`. Copy it somewhere else, you will need it.
2. Install an APK release for VB Arena. You will find the releases [here](http://github.com/nacabaro/vbhelper/releases). Download the latest release and install its APK.
Note, in the current stage of the project, you will have to delete the old application from your device. If the app keeps crashing after installing, clear application data and storage.
3. Import secrets in the app. These secrets will allow the app to talk to the watch. On the main screen, click on the gear icon, then `Import secrets`.
You will be prompted to choose a file. Choose the APK file that was previously obtained.
4. Import cards. Due to copyright laws, we cannot offer the characters and sprites themselves in the application. In order to import the cards do the following.
1. Using your own DiM/BEm cards, dump the cards to your device. You can get an in-depth tutorial in [here](http://mrblinky.net/digimon/vb/dimcardtool/dimcardtool.html). You can download the dump tool from [here](http://mrblinky.net/digimon/vb/dimcardtool/)
2. Once installed the tool and drivers, open the tool, connect your DiM/BEm reader hardware to yout computer and click on Read card.
3. Transfer the resulting file to your mobile device. You can put them anywhere, as long as they are accessible. My recommendation is to put them under a folder called `Cards` in your `Internal storage` or `SD Card`
4. In the app, click on import card. Next choose the BIN file corresponding to the card you want to import.
**Note: if you do not import the card, whenever you attempt to read a character from th watch, the character you read will get deleted.**
5. App will now be ready to be used.
## Planned features
- Online battles, undegoing development by `lightheel`.
- VitalWear compatibility, undergoing development by `cfogrady`.
- Support for multiple languages, not yet started.
- Database backup/restore.
## Credits
- `cyanic` for helping us understand more about the VB connection protocol.
- `cfogrady` for making both [`VB-DIM-Reader`](https://github.com/cfogrady/VB-DIM-Reader) and [`lib-vb-nfc`](https://github.com/cfogrady/lib-vb-nfc)
- `lightheel` for working on the online component in the application, both server and battle client.
- `shvstrz` for the app icon.
+2 -1
View File
@@ -15,7 +15,7 @@ android {
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0"
versionName = "Alpha 0.6.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -91,4 +91,5 @@ dependencies {
implementation("com.google.android.material:material:1.2.0")
implementation(libs.protobuf.javalite)
implementation("androidx.compose.material:material")
implementation("androidx.datastore:datastore-preferences:1.1.7")
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -1,44 +1,26 @@
package com.github.nacabaro.vbhelper
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vb.dim.card.BemCard
import com.github.cfogrady.vb.dim.card.DimReader
import com.github.nacabaro.vbhelper.navigation.AppNavigation
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.characters.Card
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
import com.github.nacabaro.vbhelper.screens.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.cardScreen.CardScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.util.GregorianCalendar
class MainActivity : ComponentActivity() {
private var nfcCharacter = MutableStateFlow<NfcCharacter?>(null)
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
private val onActivityLifecycleListeners = HashMap<String, ActivityLifecycleListener>()
private fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener) {
@@ -53,30 +35,40 @@ class MainActivity : ComponentActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
registerFileActivityResult()
val application = applicationContext as VBHelper
val scanScreenController = ScanScreenControllerImpl(
application.container.dataStoreSecretsRepository.secretsFlow,
this::handleReceivedNfcCharacter,
this,
this::registerActivityLifecycleListener,
this::unregisterActivityLifecycleListener
)
val settingsScreenController = SettingsScreenControllerImpl(this)
val itemsScreenController = ItemsScreenControllerImpl(this)
val adventureScreenController = AdventureScreenControllerImpl(this)
val storageScreenController = StorageScreenControllerImpl(this)
val homeScreenController = HomeScreenControllerImpl(this)
val spriteViewerController = SpriteViewerControllerImpl(this)
val cardScreenController = CardScreenControllerImpl(this)
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
VBHelperTheme {
MainApplication(
scanScreenController = scanScreenController,
settingsScreenController = settingsScreenController,
itemsScreenController = itemsScreenController
itemsScreenController = itemsScreenController,
adventureScreenController = adventureScreenController,
homeScreenController = homeScreenController,
storageScreenController = storageScreenController,
spriteViewerController = spriteViewerController,
cardScreenController = cardScreenController
)
}
}
Log.i("MainActivity", "Activity onCreated")
}
@@ -96,240 +88,28 @@ class MainActivity : ComponentActivity() {
}
}
private fun registerFileActivityResult() {
activityResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
lifecycleScope.launch {
val application = applicationContext as VBHelper
val storageRepository = application.container.db
if (it.resultCode != RESULT_OK) {
Toast.makeText(applicationContext, "Import operation cancelled.", Toast.LENGTH_SHORT).show()
}
val contentResolver = applicationContext.contentResolver
val inputStream = contentResolver.openInputStream(it.data!!.data!!)
inputStream.use { fileReader ->
val dimReader = DimReader()
val card = dimReader.readCard(fileReader, false)
Log.i("MainActivity", "Card name: ${card is BemCard}")
val cardModel = Card(
dimId = card.header.dimId,
logo = card.spriteData.sprites[0].pixelData,
name = card.spriteData.text, // TODO Make user write card name
stageCount = card.adventureLevels.levels.size,
logoHeight = card.spriteData.sprites[0].height,
logoWidth = card.spriteData.sprites[0].width,
isBEm = card is BemCard
)
val dimId = storageRepository
.dimDao()
.insertNewDim(cardModel)
val characters = card.characterStats.characterEntries
var spriteCounter = when (card is BemCard) {
true -> 55
false -> 10
}
val domainCharacters = mutableListOf<Character>()
for (index in 0 until characters.size) {
domainCharacters.add(
Character(
dimId = dimId,
monIndex = index,
name = card.spriteData.sprites[spriteCounter].pixelData,
stage = characters[index].stage,
attribute = 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
)
)
// TODO: Improve this
if (card is BemCard) {
spriteCounter += 14
} else {
when (index) {
0 -> spriteCounter += 6
1 -> spriteCounter += 7
else -> spriteCounter += 14
}
}
}
storageRepository
.characterDao()
.insertCharacter(*domainCharacters.toTypedArray())
val sprites = card.spriteData.sprites.map { sprite ->
Sprites(
id = 0,
sprite = sprite.pixelData,
width = sprite.width,
height = sprite.height
)
}
storageRepository
.characterDao()
.insertSprite(*sprites.toTypedArray())
}
inputStream?.close()
Toast.makeText(applicationContext, "Import successful!", Toast.LENGTH_SHORT).show()
}
}
}
@Composable
private fun MainApplication(
scanScreenController: ScanScreenControllerImpl,
settingsScreenController: SettingsScreenControllerImpl,
itemsScreenController: ItemsScreenControllerImpl
itemsScreenController: ItemsScreenControllerImpl,
adventureScreenController: AdventureScreenControllerImpl,
storageScreenController: StorageScreenControllerImpl,
homeScreenController: HomeScreenControllerImpl,
spriteViewerController: SpriteViewerControllerImpl,
cardScreenController: CardScreenControllerImpl
) {
AppNavigation(
applicationNavigationHandlers = AppNavigationHandlers(
settingsScreenController,
scanScreenController,
itemsScreenController
),
onClickImportCard = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
activityResultLauncher.launch(intent)
}
itemsScreenController,
adventureScreenController,
storageScreenController,
homeScreenController,
spriteViewerController,
cardScreenController
)
}
private fun handleReceivedNfcCharacter(character: NfcCharacter): String {
nfcCharacter.value = character
val importStatus = addCharacterScannedIntoDatabase()
return importStatus
}
//
/*
TODO:
- Support for regular VB
The good news is that the theory behind inserting to the database should be working
now, it's a matter of implementing the functionality to parse dim/bem cards and use my
domain model.
*/
private fun addCharacterScannedIntoDatabase(): String {
val application = applicationContext as VBHelper
val storageRepository = application.container.db
val dimData = storageRepository
.dimDao()
.getDimById(nfcCharacter.value!!.dimId.toInt())
if (dimData == null) return "Card not found"
val cardCharData = storageRepository
.characterDao()
.getCharacterByMonIndex(nfcCharacter.value!!.charIndex.toInt(), dimData.id)
val characterData = UserCharacter(
charId = cardCharData.id,
stage = nfcCharacter.value!!.stage.toInt(),
attribute = nfcCharacter.value!!.attribute,
ageInDays = nfcCharacter.value!!.ageInDays.toInt(),
nextAdventureMissionStage = nfcCharacter.value!!.nextAdventureMissionStage.toInt(),
mood = nfcCharacter.value!!.mood.toInt(),
vitalPoints = nfcCharacter.value!!.vitalPoints.toInt(),
transformationCountdown = nfcCharacter.value!!.transformationCountdownInMinutes.toInt(),
injuryStatus = nfcCharacter.value!!.injuryStatus,
trophies = nfcCharacter.value!!.trophies.toInt(),
currentPhaseBattlesWon = nfcCharacter.value!!.currentPhaseBattlesWon.toInt(),
currentPhaseBattlesLost = nfcCharacter.value!!.currentPhaseBattlesLost.toInt(),
totalBattlesWon = nfcCharacter.value!!.totalBattlesWon.toInt(),
totalBattlesLost = nfcCharacter.value!!.totalBattlesLost.toInt(),
activityLevel = nfcCharacter.value!!.activityLevel.toInt(),
heartRateCurrent = nfcCharacter.value!!.heartRateCurrent.toInt(),
characterType = when (nfcCharacter.value) {
is BENfcCharacter -> DeviceType.BEDevice
else -> DeviceType.VBDevice
},
isActive = true
)
storageRepository
.userCharacterDao()
.clearActiveCharacter()
val characterId: Long = storageRepository
.userCharacterDao()
.insertCharacterData(characterData)
if (nfcCharacter.value is BENfcCharacter) {
val beCharacter = nfcCharacter.value as BENfcCharacter
val extraCharacterData = BECharacterData(
id = characterId,
trainingHp = beCharacter.trainingHp.toInt(),
trainingAp = beCharacter.trainingAp.toInt(),
trainingBp = beCharacter.trainingBp.toInt(),
remainingTrainingTimeInMinutes = beCharacter.remainingTrainingTimeInMinutes.toInt(),
itemEffectActivityLevelValue = beCharacter.itemEffectActivityLevelValue.toInt(),
itemEffectMentalStateValue = beCharacter.itemEffectMentalStateValue.toInt(),
itemEffectMentalStateMinutesRemaining = beCharacter.itemEffectMentalStateMinutesRemaining.toInt(),
itemEffectActivityLevelMinutesRemaining = beCharacter.itemEffectActivityLevelMinutesRemaining.toInt(),
itemEffectVitalPointsChangeValue = beCharacter.itemEffectVitalPointsChangeValue.toInt(),
itemEffectVitalPointsChangeMinutesRemaining = beCharacter.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
abilityRarity = beCharacter.abilityRarity,
abilityType = beCharacter.abilityType.toInt(),
abilityBranch = beCharacter.abilityBranch.toInt(),
abilityReset = beCharacter.abilityReset.toInt(),
rank = beCharacter.abilityReset.toInt(),
itemType = beCharacter.itemType.toInt(),
itemMultiplier = beCharacter.itemMultiplier.toInt(),
itemRemainingTime = beCharacter.itemRemainingTime.toInt(),
otp0 = "", //beCharacter.value!!.otp0.toString(),
otp1 = "", //beCharacter.value!!.otp1.toString(),
minorVersion = beCharacter.characterCreationFirmwareVersion.minorVersion.toInt(),
majorVersion = beCharacter.characterCreationFirmwareVersion.majorVersion.toInt(),
)
storageRepository
.userCharacterDao()
.insertBECharacterData(extraCharacterData)
val transformationHistoryWatch = beCharacter.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.value is VBNfcCharacter) {
return "Not implemented yet"
}
return "Done reading character!"
}
}
@@ -5,11 +5,15 @@ 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.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -17,6 +21,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
@@ -27,44 +32,80 @@ 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
import androidx.compose.ui.res.stringResource
@Composable
fun CharacterEntry(
icon: BitmapData,
modifier: Modifier = Modifier,
cardIcon: BitmapData? = null,
obscure: Boolean = false,
disabled: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 3,
multiplier: Int = 4,
cardColors: CardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
),
onClick: () -> Unit = { }
) {
val bitmap = remember (icon.bitmap) {
if(obscure) icon.getObscuredBitmap() else icon.getBitmap()
}
val iconSizeMultiplier = 3
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (icon.width * multiplier / density).dp
Card(
shape = shape,
onClick = onClick,
onClick = when (disabled) {
true -> { {} }
false -> onClick
},
modifier = modifier
.aspectRatio(1f)
.padding(8.dp)
.padding(8.dp),
colors = cardColors
) {
Box(
contentAlignment = Alignment.BottomCenter,
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.padding(4.dp)
) {
Image(
bitmap = imageBitmap,
contentDescription = "Icon",
filterQuality = FilterQuality.None,
colorFilter = when (obscure) {
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
false -> null
},
modifier = Modifier
.size(dpSize)
.align(Alignment.BottomCenter)
)
if (cardIcon != null) {
val bitmap = remember (icon.bitmap) { cardIcon.getBitmap() }
val iconBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val dpSize = (icon.width * iconSizeMultiplier /density).dp
Image(
bitmap = iconBitmap,
contentDescription = "Card icon",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
.align(Alignment.BottomEnd)
.padding(8.dp)
)
}
}
}
}
@@ -106,3 +147,113 @@ fun ItemDisplay(
}
}
}
@Composable
fun SpecialMissionsEntry(
specialMission: SpecialMissions,
modifier: Modifier = Modifier,
onClickCard: () -> Unit = { },
) {
val textValue = when (specialMission.missionType) {
SpecialMission.Type.NONE -> stringResource(R.string.special_mission_none)
SpecialMission.Type.STEPS -> stringResource(
R.string.special_mission_steps,
specialMission.goal
)
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles,
specialMission.goal
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins,
specialMission.goal
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals,
specialMission.goal
)
}
val progress = if (specialMission.status == SpecialMission.Status.COMPLETED) {
specialMission.goal
} else {
specialMission.progress
}
val completion = when (specialMission.missionType) {
SpecialMission.Type.NONE -> ""
SpecialMission.Type.STEPS -> stringResource(
R.string.special_mission_steps_progress,
progress
)
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles_progress,
progress
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins_progress,
progress
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals_progress,
progress
)
}
val icon = when (specialMission.missionType) {
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 || specialMission.status == SpecialMission.Status.FAILED) {
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 = stringResource(R.string.special_mission_icon_content_description),
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,9 @@ fun TopBanner(
modifier: Modifier = Modifier,
onGearClick: (() -> Unit)? = null,
onBackClick: (() -> Unit)? = null,
onScanClick: (() -> Unit)? = null
onScanClick: (() -> Unit)? = null,
onAdventureClick: (() -> Unit)? = null,
onModifyClick: (() -> Unit)? = null
) {
Box( // Use Box to overlay elements
modifier = modifier
@@ -36,29 +38,51 @@ fun TopBanner(
textAlign = TextAlign.Center,
fontSize = 24.sp,
modifier = Modifier
.align(Alignment.Center) // Center the text
.align(Alignment.Center)
)
if (onGearClick != null) {
IconButton(
onClick = onGearClick,
modifier = Modifier
.align(Alignment.CenterEnd) // Place gear icon at the end
.align(Alignment.CenterEnd)
) {
Icon(
painter = painterResource(R.drawable.baseline_settings_24), // Use a gear icon
painter = painterResource(R.drawable.baseline_settings_24),
contentDescription = "Settings"
)
}
} else if (onAdventureClick != null) {
IconButton(
onClick = onAdventureClick,
modifier = Modifier
.align(Alignment.CenterEnd)
) {
Icon(
painter = painterResource(R.drawable.baseline_fort_24),
contentDescription = "Adventure"
)
}
} else if (onModifyClick != null) {
IconButton(
onClick = onModifyClick,
modifier = Modifier
.align(Alignment.CenterEnd)
) {
Icon(
painter = painterResource(R.drawable.baseline_edit_24),
contentDescription = "Adventure"
)
}
}
if (onScanClick != null) {
IconButton(
onClick = onScanClick,
modifier = Modifier
.align(Alignment.CenterStart) // Place gear icon at the end
.align(Alignment.CenterStart)
) {
Icon(
painter = painterResource(R.drawable.baseline_nfc_24), // Use a gear icon
painter = painterResource(R.drawable.baseline_nfc_24),
contentDescription = "Scan"
)
}
@@ -66,10 +90,10 @@ fun TopBanner(
IconButton(
onClick = onBackClick,
modifier = Modifier
.align(Alignment.CenterStart) // Place gear icon at the end
.align(Alignment.CenterStart)
) {
Icon(
painter = painterResource(R.drawable.baseline_arrow_back_24), // Use a gear icon
painter = painterResource(R.drawable.baseline_arrow_back_24),
contentDescription = "Settings"
)
}
@@ -0,0 +1,48 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
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 CardCharacter c ON uc.charId = c.id
JOIN Sprite s ON s.id = c.spriteId
JOIN Card d ON c.cardId = d.id
JOIN Adventure a ON uc.id = a.characterId
"""
)
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>>
@Query("""
DELETE FROM Adventure
WHERE characterId = :characterId
""")
suspend fun deleteAdventure(characterId: Long)
}
@@ -0,0 +1,59 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CardAdventureDao {
@Query("""
INSERT INTO
CardAdventure (cardId, characterId, steps, bossAp, bossHp, bossDp, bossBp)
SELECT
:cardId,
cc.id,
:steps,
:bossAp,
:bossHp,
:bossDp,
:bossBp
FROM
CardCharacter cc
WHERE
cc.charaIndex = :characterId AND
cc.cardId = :cardId
""")
suspend fun insertNewAdventure(
cardId: Long,
characterId: Int,
steps: Int,
bossAp: Int,
bossHp: Int,
bossDp: Int,
bossBp: Int?
)
@Query("""
SELECT
cc.nameSprite as characterName,
cc.nameWidth as characterNameWidth,
cc.nameHeight as characterNameHeight,
s.spriteIdle1 as characterIdleSprite,
s.width as characterIdleSpriteWidth,
s.height as characterIdleSpriteHeight,
ca.bossAp as characterAp,
ca.bossBp as characterBp,
ca.bossDp as characterDp,
ca.bossHp as characterHp,
ca.steps as steps
FROM CardCharacter cc
JOIN Sprite s ON cc.spriteId = s.id
JOIN CardAdventure ca ON cc.id = ca.characterId
WHERE
cc.cardId = :cardId
""")
fun getAdventureForCard(
cardId: Long
): Flow<List<CardDtos.CardAdventureWithSprites>>
}
@@ -0,0 +1,49 @@
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.card.Card
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CardDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertNewCard(card: Card): Long
@Query("SELECT * FROM Card WHERE cardId = :id")
fun getCardByCardId(id: Int): List<Card>
@Query("SELECT * FROM Card WHERE id = :id")
fun getCardById(id: Long): Card?
@Query(
"""
SELECT ca.*
FROM Card ca
JOIN CardCharacter ch ON ca.id = ch.cardId
JOIN UserCharacter uc ON ch.id = uc.charId
WHERE uc.id = :id
"""
)
fun getCardByCharacterId(id: Long): Flow<Card>
@Query("UPDATE Card SET name = :newName WHERE id = :id")
suspend fun renameCard(id: Int, newName: String)
@Query("DELETE FROM Card WHERE id = :id")
suspend fun deleteCard(id: Long)
@Query("""
SELECT
c.logo as cardIcon,
c.logoWidth as cardIconWidth,
c.logoHeight as cardIconHeight
FROM Card c
JOIN CardCharacter cc ON cc.cardId = c.id
WHERE cc.id = :charaId
""")
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon>
}
@@ -0,0 +1,48 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CardFusionsDao {
@Query("""
INSERT INTO
CardFusions (
fromCharaId,
attribute,
toCharaId
)
SELECT
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :fromCharaId),
:attribute,
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaId)
""")
suspend fun insertNewFusion(
cardId: Long,
fromCharaId: Int,
attribute: NfcCharacter.Attribute,
toCharaId: Int
)
@Query("""
SELECT
cf.toCharaId as charaId,
cf.fromCharaId as fromCharaId,
s.spriteIdle1 as spriteIdle,
cc.attribute as attribute,
s.width as spriteWidth,
s.height as spriteHeight,
d.discoveredOn as discoveredOn,
cf.attribute as fusionAttribute
FROM CardFusions cf
JOIN CardCharacter cc ON cc.id = cf.toCharaId
JOIN Sprite s ON s.id = cc.id
LEFT JOIN Dex d ON d.id = cc.id
WHERE cf.fromCharaId = :charaId
ORDER BY cc.charaIndex
""")
fun getFusionsForCharacter(charaId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>>
}
@@ -0,0 +1,29 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CardProgressDao {
@Query("""
UPDATE CardProgress
SET
currentStage = :currentStage,
unlocked = :unlocked
WHERE cardId = :cardId AND
currentStage < :currentStage
""")
fun updateCardProgress(currentStage: Int, cardId: Long, unlocked: Boolean)
@Query(
"SELECT currentStage FROM CardProgress WHERE cardId = :cardId"
)
fun getCardProgress(cardId: Long): Flow<Int>
@Insert
fun insertCardProgress(cardProgress: CardProgress)
}
@@ -3,49 +3,88 @@ package com.github.nacabaro.vbhelper.daos
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.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CharacterDao {
@Insert
suspend fun insertCharacter(vararg characterData: Character)
suspend fun insertCharacter(vararg characterData: CardCharacter)
@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
@Query("SELECT * FROM CardCharacter WHERE charaIndex = :monIndex AND cardId = :dimId LIMIT 1")
fun getCharacterByMonIndex(monIndex: Int, dimId: Long): CardCharacter
@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
FROM Character c
d.cardId as cardId,
c.charaIndex as charId,
c.stage as stage,
c.attribute as attribute,
cp.currentStage as currentStage
FROM CardCharacter c
JOIN UserCharacter uc ON c.id = uc.charId
JOIN Card d ON c.dimId = d.id
WHERE uc.id = :charId
JOIN Card d ON c.cardId = d.id
JOIN CardProgress cp ON d.id = cp.cardId
WHERE c.id = :charId
"""
)
suspend fun getCharacterInfo(charId: Long): CharacterDtos.DiMInfo
suspend fun getCharacterInfo(charId: Long): CharacterDtos.CardCharacterInfo
@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)
@Query(
"""
INSERT INTO PossibleTransformations (charaId, requiredVitals, requiredTrophies, requiredBattles, requiredWinRate, changeTimerHours, requiredAdventureLevelCompleted, toCharaId)
SELECT
(SELECT id FROM CardCharacter WHERE charaIndex = :fromChraraIndex AND cardId = :cardId),
:requiredVitals,
:requiredTrophies,
:requiredBattles,
:requiredWinRate,
:changeTimerHours,
:requiredAdventureLevelCompleted,
(SELECT id FROM CardCharacter WHERE charaIndex = :toChraraIndex AND cardId = :cardId)
"""
)
suspend fun insertPossibleTransformation(
fromChraraIndex: Int,
toChraraIndex: Int,
cardId: Long,
requiredVitals: Int,
requiredTrophies: Int,
requiredBattles: Int,
changeTimerHours: Int,
requiredWinRate: Int,
requiredAdventureLevelCompleted: Int
)
@Query(
"""
SELECT
pt.charaId as fromCharaId,
pt.toCharaId as charaId,
s.spriteIdle1 as spriteIdle,
s.width as spriteWidth,
s.height as spriteHeight,
d.discoveredOn as discoveredOn,
pt.requiredTrophies as requiredTrophies,
pt.requiredVitals as requiredVitals,
pt.requiredBattles as requiredBattles,
pt.requiredWinRate as requiredWinRate,
pt.changeTimerHours as changeTimerHours,
pt.requiredAdventureLevelCompleted as requiredAdventureLevelCompleted
FROM
PossibleTransformations pt
JOIN CardCharacter c on pt.toCharaId = c.id
JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Dex d ON d.id = pt.toCharaId
WHERE
pt.charaId = :characterId
"""
)
fun getEvolutionRequirementsForCard(characterId: Long): Flow<List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>>
}
@@ -4,41 +4,57 @@ import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface DexDao {
@Query("""
@Query(
"""
INSERT OR IGNORE INTO Dex(id, discoveredOn)
VALUES (
(SELECT id FROM Character WHERE monIndex = :charIndex AND dimId = :cardId),
(SELECT id FROM CardCharacter WHERE charaIndex = :charIndex AND cardId = :cardId),
:discoveredOn
)
""")
"""
)
fun insertCharacter(charIndex: Int, cardId: Long, discoveredOn: Long)
@Query("""
@Query(
"""
SELECT
c.id AS id,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.discoveredOn AS discoveredOn
FROM character c
s.spriteIdle1 AS spriteIdle,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.nameSprite AS nameSprite,
c.nameWidth AS nameSpriteWidth,
c.nameHeight AS nameSpriteHeight,
d.discoveredOn AS discoveredOn,
c.baseHp as baseHp,
c.baseBp as baseBp,
c.baseAp as baseAp,
c.stage as stage,
c.attribute as attribute
FROM CardCharacter c
JOIN Sprite s ON c.spriteId = s.id
LEFT JOIN dex d ON c.id = d.id
WHERE c.dimId = :cardId
""")
suspend fun getSingleCardProgress(cardId: Long): List<CharacterDtos.CardProgress>
WHERE c.cardId = :cardId
"""
)
fun getSingleCardProgress(cardId: Long): Flow<List<CharacterDtos.CardCharaProgress>>
@Query("""
@Query(
"""
SELECT
c.id as cardId,
c.name as cardName,
c.logo as cardLogo,
c.logoWidth as logoWidth,
c.logoHeight as logoHeight,
(SELECT COUNT(*) FROM Character cc WHERE cc.dimId = c.id) AS totalCharacters,
(SELECT COUNT(*) FROM Dex d JOIN Character cc ON d.id = cc.id WHERE cc.dimId = c.id AND d.discoveredOn IS NOT NULL) AS obtainedCharacters
(SELECT COUNT(*) FROM CardCharacter cc WHERE cc.cardId = c.id) AS totalCharacters,
(SELECT COUNT(*) FROM Dex d JOIN CardCharacter cc ON d.id = cc.id WHERE cc.cardId = c.id AND d.discoveredOn IS NOT NULL) AS obtainedCharacters
FROM Card c
""")
suspend fun getCardsWithProgress(): List<CardDtos.CardProgress>
"""
)
fun getCardsWithProgress(): Flow<List<CardDtos.CardProgress>>
}
@@ -1,19 +0,0 @@
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.Card
@Dao
interface DiMDao {
@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")
fun getDimById(id: Int): Card?
}
@@ -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)
}
@@ -3,43 +3,52 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface ItemDao {
@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>
"""
)
fun getAllItems(): Flow<List<ItemDtos.ItemsWithQuantities>>
@Query("""
SELECT Items.*, UserItems.quantity
@Query(
"""
SELECT *
FROM Items
JOIN UserItems ON Items.id = UserItems.itemId
""")
suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>
WHERE quantity > 0
"""
)
fun getAllUserItems(): Flow<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,10 +5,15 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.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
import kotlinx.coroutines.flow.Flow
@Dao
interface UserCharacterDao {
@@ -18,6 +23,9 @@ interface UserCharacterDao {
@Insert
fun insertBECharacterData(characterData: BECharacterData)
@Insert
fun insertVBCharacterData(characterData: VBCharacterData)
@Upsert
fun updateCharacter(character: UserCharacter)
@@ -27,57 +35,115 @@ interface UserCharacterDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertTransformationHistory(vararg transformationHistory: TransformationHistory)
@Query("""
@Upsert
fun insertSpecialMissions(vararg specialMissions: SpecialMissions)
@Query(
"""
SELECT
c.id AS id,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
c.monIndex AS monIndex,
s.spriteIdle1 AS spriteIdle,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.charaIndex AS monIndex,
t.transformationDate AS transformationDate
FROM TransformationHistory t
JOIN Character c ON c.id = t.stageId
JOIN CardCharacter c ON c.id = t.stageId
JOIN Sprite s ON s.id = c.spriteId
WHERE monId = :monId
""")
fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?
"""
)
fun getTransformationHistory(monId: Long): Flow<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.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId
JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id
"""
)
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
fun getAllCharacters(): Flow<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.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = 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
fun getBeData(id: Long): Flow<BECharacterData>
@Query("SELECT * FROM VBCharacterData WHERE id = :id")
fun getVbData(id: Long): Flow<VBCharacterData>
@Query("SELECT * FROM SpecialMissions WHERE characterId = :id")
fun getSpecialMissions(id: Long): Flow<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.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = 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?
fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?>
@Query("DELETE FROM UserCharacter WHERE id = :id")
fun deleteCharacterById(id: Long)
@@ -87,4 +153,85 @@ interface UserCharacterDao {
@Query("UPDATE UserCharacter SET isActive = 1 WHERE id = :id")
fun setActiveCharacter(id: Long)
@Query(
"""
SELECT c.*
FROM CardCharacter c
join UserCharacter uc on c.id = uc.charId
where uc.id = :charId
LIMIT 1
"""
)
suspend fun getCharacterInfo(charId: Long): CardCharacter
@Query(
"""
INSERT INTO TransformationHistory(monId, stageId, transformationDate)
VALUES
(:monId,
(SELECT id FROM CardCharacter WHERE charaIndex = :stage AND cardId = :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.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId
JOIN Sprite s ON s.id = c.spriteId
LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.characterType = "BEDevice"
"""
)
suspend fun getBECharacters(): 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.nameSprite as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId
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,67 @@ 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.CardAdventureDao
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.CardFusionsDao
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.Background
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardAdventure
import com.github.nacabaro.vbhelper.domain.card.CardFusions
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.card.PossibleTransformations
import com.github.nacabaro.vbhelper.domain.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,
Character::class,
Sprites::class,
CardProgress::class,
CardCharacter::class,
CardAdventure::class,
CardFusions::class,
Sprite::class,
UserCharacter::class,
BECharacterData::class,
VBCharacterData::class,
SpecialMissions::class,
TransformationHistory::class,
VitalsHistory::class,
Dex::class,
Items::class,
UserItems::class
Adventure::class,
Background::class,
PossibleTransformations::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
abstract fun cardAdventureDao(): CardAdventureDao
abstract fun cardFusionsDao(): CardFusionsDao
}
@@ -1,9 +1,11 @@
package com.github.nacabaro.vbhelper.di
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
interface AppContainer {
val db: AppDatabase
val dataStoreSecretsRepository: DataStoreSecretsRepository
val currencyRepository: CurrencyRepository
}
@@ -1,20 +1,27 @@
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.dataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import androidx.room.Room
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.AppContainer
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
import com.github.nacabaro.vbhelper.source.SecretsSerializer
import com.github.nacabaro.vbhelper.source.proto.Secrets
private const val SECRETS_DATA_STORE_NAME = "secrets.pb"
private const val USER_PREFERENCES_NAME = "user_preferences"
val Context.secretsStore: DataStore<Secrets> by dataStore(
fileName = SECRETS_DATA_STORE_NAME,
serializer = SecretsSerializer
)
val Context.currencyStore: DataStore<Preferences> by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
class DefaultAppContainer(private val context: Context) : AppContainer {
override val db: AppDatabase by lazy {
@@ -29,5 +36,6 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
override val currencyRepository = CurrencyRepository(context.currencyStore)
}
@@ -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
)
@@ -3,7 +3,6 @@ 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.Character
@Entity(
foreignKeys = [
@@ -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
)
@@ -3,7 +3,6 @@ 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.Character
@Entity(
foreignKeys = [
@@ -0,0 +1,23 @@
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = Card::class,
parentColumns = ["id"],
childColumns = ["cardId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Background (
@PrimaryKey(autoGenerate = true) val id: Long,
val cardId: Long,
val background: ByteArray,
val backgroundWidth: Int,
val backgroundHeight: Int
)
@@ -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,
@@ -0,0 +1,32 @@
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["characterId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Card::class,
parentColumns = ["id"],
childColumns = ["cardId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class CardAdventure(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val cardId: Long,
val characterId: Long,
val steps: Int,
val bossHp: Int,
val bossAp: Int,
val bossDp: Int,
val bossBp: Int?
)
@@ -0,0 +1,44 @@
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.domain.characters.Sprite
@Entity(
foreignKeys = [
ForeignKey(
entity = Card::class,
parentColumns = ["id"],
childColumns = ["cardId"],
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 CardCharacter (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val cardId: Long,
val spriteId: Long,
val charaIndex: Int,
val stage: Int, // These should be replaced with enums
val attribute: NfcCharacter.Attribute, // This one too
val baseHp: Int,
val baseBp: Int,
val baseAp: Int,
val nameSprite: ByteArray,
val nameWidth: Int,
val nameHeight: Int
)
@@ -0,0 +1,29 @@
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter
@Entity(
foreignKeys = [
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["fromCharaId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["toCharaId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class CardFusions(
@PrimaryKey(autoGenerate = true) val id: Long,
val fromCharaId: Long,
val attribute: NfcCharacter.Attribute,
val toCharaId: Long
)
@@ -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,33 @@
package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["charaId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["toCharaId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class PossibleTransformations (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
var charaId: Long,
val requiredVitals: Int,
val requiredTrophies: Int,
val requiredBattles: Int,
val requiredWinRate: Int,
val changeTimerHours: Int,
val requiredAdventureLevelCompleted: Int,
val toCharaId: Long?
)
@@ -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
)
@@ -1,33 +0,0 @@
package com.github.nacabaro.vbhelper.domain.characters
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.ForeignKey
@Entity(
foreignKeys = [
ForeignKey(
entity = Card::class,
parentColumns = ["id"],
childColumns = ["dimId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Character (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val dimId: Long,
val monIndex: Int,
val name: ByteArray,
val stage: Int, // These should be replaced with enums
val attribute: Int, // 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
)
@@ -3,11 +3,12 @@ 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.card.CardCharacter
@Entity(
foreignKeys = [
ForeignKey(
entity = Character::class,
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["id"],
onDelete = ForeignKey.CASCADE
@@ -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
)
@@ -3,7 +3,7 @@ package com.github.nacabaro.vbhelper.domain.device_data
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
@Entity(
foreignKeys = [
@@ -14,7 +14,7 @@ import com.github.nacabaro.vbhelper.domain.characters.Character
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Character::class,
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["stageId"],
onDelete = ForeignKey.CASCADE
@@ -5,25 +5,25 @@ import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.utils.DeviceType
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
@Entity(
foreignKeys = [
ForeignKey(
entity = Character::class,
entity = CardCharacter::class,
parentColumns = ["id"],
childColumns = ["charId"],
onDelete = ForeignKey.CASCADE
)
]
)
/**
* 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
)
@@ -10,4 +10,24 @@ object CardDtos {
val totalCharacters: Int,
val obtainedCharacters: Int,
)
data class CardAdventureWithSprites (
val characterName: ByteArray,
val characterNameWidth: Int,
val characterNameHeight: Int,
val characterIdleSprite: ByteArray,
val characterIdleSpriteWidth: Int,
val characterIdleSpriteHeight: Int,
val characterAp: Int,
val characterBp: Int?,
val characterDp: Int,
val characterHp: Int,
val steps: Int,
)
data class CardIcon (
val cardIcon: ByteArray,
val cardIconWidth: Int,
val cardIconHeight: Int
)
}
@@ -11,7 +11,76 @@ 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,
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 spriteIdle2: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val nameSprite: ByteArray,
val nameSpriteWidth: Int,
val nameSpriteHeight: Int,
val isBemCard: Boolean,
val isInAdventure: Boolean,
val active: Boolean
)
data class CardCharacterInfo(
val cardId: Long,
val charId: Int,
val stage: Int,
val attribute: NfcCharacter.Attribute,
val currentStage: Int
)
data class TransformationHistory(
val id: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val monIndex: Int,
val transformationDate: Long
)
data class CardCharaProgress(
val id: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val nameSprite: ByteArray,
val nameSpriteWidth: Int,
val nameSpriteHeight: Int,
val discoveredOn: Long?,
val baseHp: Int,
val baseBp: Int,
val baseAp: Int,
val stage: Int,
val attribute: NfcCharacter.Attribute,
)
data class CardProgress(
val id: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
)
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,
@@ -27,28 +96,33 @@ object CharacterDtos {
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val isBemCard: Boolean
val isBemCard: Boolean,
val finishesAdventure: Long,
val originalTimeInMinutes: Long
)
data class DiMInfo(
val cardId: Int,
val charId: Int
)
data class TransformationHistory(
val id: Long,
data class EvolutionRequirementsWithSpritesAndObtained(
val charaId: Long,
val fromCharaId: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val monIndex: Int,
val transformationDate: Long
val discoveredOn: Long?,
val requiredTrophies: Int,
val requiredVitals: Int,
val requiredBattles: Int,
val requiredWinRate: Int,
val changeTimerHours: Int,
val requiredAdventureLevelCompleted: Int
)
data class CardProgress(
val id: Long,
data class FusionsWithSpritesAndObtained(
val charaId: Long,
val fromCharaId: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val discoveredOn: Long?
val discoveredOn: Long?,
val fusionAttribute: NfcCharacter.Attribute
)
}
@@ -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,36 +1,59 @@
package com.github.nacabaro.vbhelper.navigation
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.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.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
import com.github.nacabaro.vbhelper.screens.cardScreen.CardsScreen
import com.github.nacabaro.vbhelper.screens.cardScreen.CardViewScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen
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.cardScreen.CardAdventureScreen
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
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,
val cardScreenController: CardScreenControllerImpl
)
@Composable
fun AppNavigation(
applicationNavigationHandlers: AppNavigationHandlers,
onClickImportCard: () -> Unit,
) {
val navController = rememberNavController()
@@ -42,55 +65,81 @@ 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()) }
val launchedFromHomeScreen = (characterIdString?.toLongOrNull() == null)
if (characterId == null) {
val context = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(context.container.db)
val characterData by storageRepository.getActiveCharacter().collectAsState(null)
if (characterData != null) {
characterId = characterData!!.id
}
}
ScanScreen(
navController = navController,
scanScreenController = applicationNavigationHandlers.scanScreenController,
characterId = characterId
characterId = characterId,
launchedFromHomeScreen = launchedFromHomeScreen
)
}
composable(NavigationItems.Dex.route) {
DexScreen(
navController = navController
CardsScreen(
navController = navController,
cardScreenController = applicationNavigationHandlers.cardScreenController
)
}
composable(NavigationItems.Settings.route) {
SettingsScreen(
navController = navController,
settingsScreenController = applicationNavigationHandlers.settingsScreenController,
onClickImportCard = onClickImportCard,
settingsScreenController = applicationNavigationHandlers.settingsScreenController
)
}
composable(NavigationItems.Viewer.route) {
SpriteViewer(
navController = navController
navController = navController,
spriteViewerController = applicationNavigationHandlers.spriteViewerController
)
}
composable(NavigationItems.CardView.route) {
val cardId = it.arguments?.getString("cardId")
if (cardId != null) {
DiMScreen(
CardViewScreen(
navController = navController,
dimId = cardId.toLong()
cardId = cardId.toLong()
)
}
}
@@ -110,6 +159,29 @@ fun AppNavigation(
)
}
}
composable(NavigationItems.Adventure.route) {
AdventureScreen(
navController = navController,
storageScreenController = applicationNavigationHandlers
.adventureScreenController
)
}
composable(NavigationItems.Credits.route) {
CreditsScreen(
navController = navController
)
}
composable(NavigationItems.CardAdventure.route) {
val cardId = it.arguments?.getString("cardId")
if (cardId != null) {
CardAdventureScreen(
navController = navController,
cardId = cardId.toLong(),
cardScreenController = applicationNavigationHandlers
.cardScreenController
)
}
}
}
}
}
@@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.compose.ui.res.stringResource
@Composable
fun BottomNavigationBar(navController: NavController) {
@@ -19,14 +19,15 @@ fun BottomNavigationBar(navController: NavController) {
NavigationItems.Dex,
NavigationItems.Storage,
)
NavigationBar {
val currentBackStackEntry = navController.currentBackStackEntryAsState()
val currentRoute = currentBackStackEntry.value?.destination?.route
items.forEach { item ->
NavigationBarItem (
icon = { Icon(painter = painterResource(item.icon), contentDescription = item.label) },
label = { Text(item.label) },
icon = { Icon(painter = painterResource(item.icon), contentDescription = stringResource(item.label)) },
label = { Text(text = stringResource(item.label)) },
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
@@ -1,22 +1,101 @@
package com.github.nacabaro.vbhelper.navigation
import com.github.nacabaro.vbhelper.R
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
sealed class NavigationItems (
var route: String,
var icon: Int,
var label: String
sealed class NavigationItems(
val route: String,
@DrawableRes val icon: Int,
@StringRes val label: Int
) {
object Scan : NavigationItems("Scan/{characterId}", R.drawable.baseline_nfc_24, "Scan")
object Battles : NavigationItems("Battle", R.drawable.baseline_swords_24, "Battle")
object Home : NavigationItems("Home", R.drawable.baseline_cottage_24, "Home")
object Dex : NavigationItems("Dex", R.drawable.baseline_menu_book_24, "Dex")
object Storage : NavigationItems("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings")
object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer")
object CardView : NavigationItems("Card/{cardId}", R.drawable.baseline_image_24, "Card")
object Items : NavigationItems("Items", R.drawable.baseline_data_24, "Items")
object MyItems : NavigationItems("MyItems", R.drawable.baseline_data_24, "My items")
object ItemsStore : NavigationItems("ItemsStore", R.drawable.baseline_data_24, "Items store")
object ApplyItem : NavigationItems("ApplyItem/{itemId}", R.drawable.baseline_data_24, "Apply item")
object Scan : NavigationItems(
"Scan/{characterId}",
R.drawable.baseline_nfc_24,
R.string.nav_scan
)
object Battles : NavigationItems(
"Battle",
R.drawable.baseline_swords_24,
R.string.nav_battle
)
object Home : NavigationItems(
"Home",
R.drawable.baseline_cottage_24,
R.string.nav_home
)
object Dex : NavigationItems(
"Dex",
R.drawable.baseline_menu_book_24,
R.string.nav_dex
)
object CardAdventure : NavigationItems(
"CardAdventure/{cardId}",
R.drawable.baseline_fort_24,
R.string.nav_card_adventure
)
object Storage : NavigationItems(
"Storage",
R.drawable.baseline_catching_pokemon_24,
R.string.nav_storage
)
object Settings : NavigationItems(
"Settings",
R.drawable.baseline_settings_24,
R.string.nav_settings
)
object Viewer : NavigationItems(
"Viewer",
R.drawable.baseline_image_24,
R.string.nav_viewer
)
object CardView : NavigationItems(
"Card/{cardId}",
R.drawable.baseline_image_24,
R.string.nav_card
)
object Items : NavigationItems(
"Items",
R.drawable.baseline_data_24,
R.string.nav_items
)
object MyItems : NavigationItems(
"MyItems",
R.drawable.baseline_data_24,
R.string.nav_my_items
)
object ItemsStore : NavigationItems(
"ItemsStore",
R.drawable.baseline_data_24,
R.string.nav_items_store
)
object ApplyItem : NavigationItems(
"ApplyItem/{itemId}",
R.drawable.baseline_data_24,
R.string.nav_apply_item
)
object Adventure : NavigationItems(
"Adventure",
R.drawable.baseline_fort_24,
R.string.nav_adventure
)
object Credits : NavigationItems(
"Credits",
R.drawable.baseline_data_24,
R.string.nav_credits
)
}
@@ -1,9 +1,36 @@
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
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun BattlesScreen() {
Text("Battles Screen")
Scaffold (
topBar = {
TopBanner(
text = stringResource(R.string.battles_online_title)
)
}
) { contentPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(stringResource(R.string.battles_coming_soon))
}
}
}
@@ -1,84 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import androidx.compose.foundation.layout.fillMaxWidth
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.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.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
import kotlinx.coroutines.launch
@Composable
fun DexScreen(
navController: NavController
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db)
val cardList = remember { mutableStateOf<List<CardDtos.CardProgress>>(emptyList()) }
LaunchedEffect(dexRepository) {
coroutineScope.launch {
val newDimList = dexRepository.getAllDims()
cardList.value = newDimList // Replace the entire list atomically
}
}
Scaffold (
topBar = {
TopBanner(
text = "Discovered characters",
onGearClick = {
navController.navigate(NavigationItems.Viewer.route)
}
)
}
) { contentPadding ->
LazyColumn (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(cardList.value) {
DexDiMEntry(
name = it.cardName,
logo = BitmapData(
bitmap = it.cardLogo,
width = it.logoWidth,
height = it.logoHeight
),
onClick = {
navController
.navigate(
NavigationItems
.CardView.route
.replace("{cardId}", "${it.cardId}")
)
},
obtainedCharacters = it.obtainedCharacters,
totalCharacters = it.totalCharacters,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
}
}
}
}
@@ -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,141 @@
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.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
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 java.time.Instant
import com.github.nacabaro.vbhelper.R
@Composable
fun AdventureScreen(
navController: NavController,
storageScreenController: AdventureScreenControllerImpl
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val storageRepository = StorageRepository(database)
val characterList by storageRepository.getAdventureCharacters().collectAsState(emptyList())
var obtainedItem by remember {
mutableStateOf<ItemDtos.PurchasedItem?>(null)
}
var obtainedCurrency by remember {
mutableStateOf(0)
}
val currentTime by produceState(initialValue = Instant.now().epochSecond) {
while (true) {
value = Instant.now().epochSecond
delay(1000)
}
}
var cancelAdventureDialog by remember {
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
}
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.adventure_title),
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
if (characterList.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(text = stringResource(R.string.adventure_empty_state))
}
} else {
LazyColumn(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(characterList) {
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, generatedCurrency ->
obtainedItem = adventureResult
obtainedCurrency = generatedCurrency
}
} else {
cancelAdventureDialog = it
}
}
)
}
}
}
}
if (obtainedItem != null) {
ObtainedItemDialog(
obtainedItem = obtainedItem!!,
obtainedCurrency = obtainedCurrency,
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, Int) -> Unit)
fun cancelAdventure(characterId: Long, onResult: () -> Unit)
}
@@ -0,0 +1,113 @@
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.flow.first
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, Int) -> Unit
) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.adventureDao()
.deleteAdventure(characterId)
val generatedCurrency = generateRandomCurrency()
val generatedItem = generateItem(characterId)
onResult(generatedItem, generatedCurrency)
}
}
private suspend fun generateRandomCurrency(): Int {
val currentValue = application.container.currencyRepository.currencyValue.first()
val random = (2..6).random() * 1000
application.container.currencyRepository.setCurrencyValue(currentValue + random)
return random
}
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
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()
.first()
.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,122 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
@Composable
fun CardAdventureEntry(
cardAdventureEntry: CardDtos.CardAdventureWithSprites,
obscure: Boolean
) {
val charaImageBitmapData = BitmapData(
bitmap = cardAdventureEntry.characterIdleSprite,
width = cardAdventureEntry.characterIdleSpriteWidth,
height = cardAdventureEntry.characterIdleSpriteHeight
).getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = obscure
)
val nameImageBitmapData = BitmapData(
bitmap = cardAdventureEntry.characterName,
width = cardAdventureEntry.characterNameWidth,
height = cardAdventureEntry.characterNameHeight
).getImageBitmap(
context = LocalContext.current,
multiplier = 3,
obscure = obscure
)
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier
.padding(8.dp)
){
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
colorFilter = when (obscure) {
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
false -> null
},
filterQuality = FilterQuality.None
)
}
Spacer(modifier = Modifier.padding(8.dp))
Column {
if (!obscure) {
Image(
bitmap = nameImageBitmapData.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.width(nameImageBitmapData.dpWidth)
.height(nameImageBitmapData.dpHeight),
filterQuality = FilterQuality.None
)
Spacer(modifier = Modifier.padding(4.dp))
Text(
text = "HP: ${cardAdventureEntry.characterHp}, DP: ${cardAdventureEntry.characterDp}, AP: ${cardAdventureEntry.characterAp}"
)
if (cardAdventureEntry.characterBp != null) {
Text(text = "BP: ${cardAdventureEntry.characterBp}")
}
Text(text = "Steps: ${cardAdventureEntry.steps}")
} else {
Text(text = "????????????????")
Text(
text = "HP: -, BP: -, AP: -"
)
if (cardAdventureEntry.characterBp != null) {
Text(text = "DP: -")
}
Text(text = "Steps: -")
}
}
}
}
}
@@ -0,0 +1,54 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun CardAdventureScreen(
navController: NavController,
cardScreenController: CardScreenControllerImpl,
cardId: Long
) {
val cardAdventureMissions by cardScreenController
.getCardAdventureMissions(cardId)
.collectAsState(emptyList())
val currentCardAdventure by cardScreenController
.getCardProgress(cardId)
.collectAsState(0)
Scaffold (
topBar = {
TopBanner(
text = stringResource(R.string.card_adventure_missions_title),
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.verticalScroll(state = rememberScrollState())
) {
cardAdventureMissions.mapIndexed { index, it ->
CardAdventureEntry(
cardAdventureEntry = it,
obscure = index > currentCardAdventure - 1
)
}
}
}
}
@@ -0,0 +1,116 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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 com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun CardEntry(
name: String,
logo: BitmapData,
obtainedCharacters: Int,
totalCharacters: Int,
onClick: () -> Unit,
displayModify: Boolean,
onClickModify: () -> Unit,
onClickDelete: () -> Unit,
modifier: Modifier = Modifier
) {
val bitmap = remember (logo.bitmap) { logo.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (logo.width * 4 / density).dp
Card (
shape = MaterialTheme.shapes.medium,
modifier = modifier,
onClick = if (!displayModify) {
onClick
} else {
{ }
}
) {
Row (
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(8.dp)
) {
Image (
bitmap = imageBitmap,
contentDescription = name,
filterQuality = FilterQuality.None,
modifier = Modifier
.padding(8.dp)
.size(dpSize)
)
Column(
modifier = Modifier
.padding(8.dp)
.weight(1f)
) {
Text(
text = name,
modifier = Modifier
)
Text(
text = stringResource(
R.string.card_entry_characters_obtained,
obtainedCharacters,
totalCharacters
),
fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
fontSize = MaterialTheme.typography.labelSmall.fontSize,
modifier = Modifier
)
}
if (displayModify) {
Row (
modifier = Modifier,
horizontalArrangement = Arrangement.End,
) {
IconButton(
onClick = onClickModify
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = stringResource(R.string.card_entry_edit)
)
}
IconButton(
onClick = onClickDelete
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = stringResource(R.string.card_entry_delete)
)
}
}
}
}
}
}
@@ -0,0 +1,13 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
interface CardScreenController {
fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit)
fun deleteCard(cardId: Long, onDeleted: () -> Unit)
fun getCardAdventureMissions(cardId: Long): Flow<List<CardDtos.CardAdventureWithSprites>>
fun getCardProgress(cardId: Long): Flow<Int>
fun getFusionsForCharacters(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>>
}
@@ -0,0 +1,55 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class CardScreenControllerImpl(
private val componentActivity: ComponentActivity,
) : CardScreenController {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
override fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit) {
componentActivity.lifecycleScope.launch {
database
.cardDao()
.renameCard(cardId.toInt(), newName)
onRenamed(newName)
}
}
override fun deleteCard(cardId: Long, onDeleted: () -> Unit) {
componentActivity.lifecycleScope.launch {
database
.cardDao()
.deleteCard(cardId)
onDeleted()
}
}
override fun getCardAdventureMissions(cardId: Long): Flow<List<CardDtos.CardAdventureWithSprites>> {
return database
.cardAdventureDao()
.getAdventureForCard(cardId)
}
override fun getCardProgress(cardId: Long): Flow<Int> {
return database
.cardProgressDao()
.getCardProgress(cardId)
}
override fun getFusionsForCharacters(characterId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>> {
return database
.cardFusionsDao()
.getFusionsForCharacter(characterId)
}
}
@@ -1,49 +1,57 @@
package com.github.nacabaro.vbhelper.screens
package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.domain.characters.Character
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.cardScreen.dialogs.DexCharaDetailsDialog
import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable
fun DiMScreen(
fun CardViewScreen(
navController: NavController,
dimId: Long
cardId: Long
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db)
val characterList = remember { mutableStateOf<List<CharacterDtos.CardProgress>>(emptyList()) }
val characterList by dexRepository.getCharactersByCardId(cardId).collectAsState(emptyList())
LaunchedEffect(dexRepository) {
coroutineScope.launch {
val newCharacterList = dexRepository.getCharactersByDimId(dimId)
characterList.value = newCharacterList
}
}
val selectedCharacter = remember { mutableStateOf<CharacterDtos.CardCharaProgress?>(null) }
Scaffold (
topBar = {
TopBanner(
text = "Discovered characters",
text = stringResource(R.string.card_view_discovered_characters),
onBackClick = {
navController.popBackStack()
},
onAdventureClick = {
navController
.navigate(route = NavigationItems
.CardAdventure
.route
.replace(
"{cardId}",
cardId.toString()
)
)
}
)
}
@@ -52,9 +60,11 @@ fun DiMScreen(
columns = GridCells.Fixed(3),
contentPadding = contentPadding
) {
items(characterList.value) { character ->
items(characterList) { character ->
CharacterEntry(
onClick = { },
onClick = {
selectedCharacter.value = character
},
obscure = character.discoveredOn == null,
icon = BitmapData(
bitmap = character.spriteIdle,
@@ -64,5 +74,15 @@ fun DiMScreen(
)
}
}
if (selectedCharacter.value != null) {
DexCharaDetailsDialog(
currentChara = selectedCharacter.value!!,
obscure = selectedCharacter.value!!.discoveredOn == null,
onClickClose = {
selectedCharacter.value = null
}
)
}
}
}
@@ -0,0 +1,137 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import android.util.Log
import androidx.compose.foundation.layout.fillMaxWidth
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.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardDeleteDialog
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardRenameDialog
import com.github.nacabaro.vbhelper.source.DexRepository
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun CardsScreen(
navController: NavController,
cardScreenController: CardScreenControllerImpl
) {
val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db)
val cardList by dexRepository.getAllDims().collectAsState(emptyList())
val selectedCard = remember { mutableStateOf<CardDtos.CardProgress?>(null) }
var clickedDelete by remember { mutableStateOf(false) }
var clickedRename by remember { mutableStateOf(false) }
var modifyCards by remember { mutableStateOf(false) }
Scaffold (
topBar = {
TopBanner(
text = stringResource(R.string.cards_my_cards_title),
onModifyClick = {
modifyCards = !modifyCards
}
)
}
) { contentPadding ->
LazyColumn (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(cardList) {
CardEntry(
name = it.cardName,
logo = BitmapData(
bitmap = it.cardLogo,
width = it.logoWidth,
height = it.logoHeight
),
onClick = {
navController
.navigate(
NavigationItems
.CardView.route
.replace("{cardId}", "${it.cardId}")
)
},
obtainedCharacters = it.obtainedCharacters,
totalCharacters = it.totalCharacters,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
displayModify = modifyCards,
onClickModify = {
selectedCard.value = it
clickedRename = true
},
onClickDelete = {
selectedCard.value = it
clickedDelete = true
}
)
}
}
}
if (clickedRename) {
CardRenameDialog(
onDismiss = {
clickedRename = false
selectedCard.value = null
},
onRename = { newName ->
Log.d("CardsScreen", "New name: $newName")
Log.d("CardsScreen", "Card: ${selectedCard.value.toString()}")
cardScreenController
.renameCard(
cardId = selectedCard.value!!.cardId,
newName = newName,
onRenamed = {
clickedRename = false
selectedCard.value = null
}
)
},
currentName = selectedCard.value!!.cardName
)
}
if (clickedDelete) {
CardDeleteDialog(
cardName = selectedCard.value!!.cardName,
onDismiss = {
clickedDelete = false
selectedCard.value = null
},
onConfirm = {
cardScreenController
.deleteCard(
cardId = selectedCard.value!!.cardId,
onDeleted = {
clickedDelete = false
selectedCard.value = null
}
)
}
)
}
}
@@ -0,0 +1,51 @@
package com.github.nacabaro.vbhelper.screens.cardScreen.dialogs
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@Composable
fun CardDeleteDialog(
cardName: String,
onDismiss: () -> Unit,
onConfirm: () -> Unit
) {
Dialog(
onDismissRequest = onDismiss
) {
Card ( ) {
Column (
modifier = Modifier
.padding(16.dp)
) {
Text(text = "Are you sure you want to delete $cardName. This action will also delete all the characters raised from this card.")
Spacer(modifier = Modifier.padding(8.dp))
Row {
Button(
onClick = {
onDismiss()
}
) {
Text(text = "Confirm")
}
Button(
onClick = {
onConfirm()
}
) {
Text(text = "Delete")
}
}
}
}
}
}
@@ -0,0 +1,52 @@
package com.github.nacabaro.vbhelper.screens.cardScreen.dialogs
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.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@Composable
fun CardRenameDialog(
onDismiss: () -> Unit,
onRename: (String) -> Unit,
currentName: String
) {
var cardName by remember { mutableStateOf(currentName) }
Dialog(
onDismissRequest = onDismiss
) {
Card ( ) {
Column (
modifier = Modifier
.padding(16.dp)
) {
TextField(
value = cardName,
onValueChange = { cardName = it }
)
Spacer(modifier = Modifier.padding(8.dp))
Button(
onClick = {
onRename(cardName)
onDismiss()
}
) {
Text(text = "Rename")
}
}
}
}
}
@@ -0,0 +1,287 @@
package com.github.nacabaro.vbhelper.screens.cardScreen.dialogs
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.source.DexRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun DexCharaDetailsDialog(
currentChara: CharacterDtos.CardCharaProgress,
obscure: Boolean,
onClickClose: () -> Unit
) {
val nameMultiplier = 3
val charaMultiplier = 4
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val dexRepository = DexRepository(database)
var showFusions by remember { mutableStateOf(false) }
val currentCharaPossibleTransformations by dexRepository
.getCharacterPossibleTransformations(currentChara.id)
.collectAsState(emptyList())
val currentCharaPossibleFusions by dexRepository
.getCharacterPossibleFusions(currentChara.id)
.collectAsState(emptyList())
val romanNumeralsStage = when (currentChara.stage) {
1 -> "II"
2 -> "III"
3 -> "IV"
4 -> "V"
5 -> "VI"
6 -> "VII"
else -> "I"
}
val charaBitmapData = BitmapData(
bitmap = currentChara.spriteIdle,
width = currentChara.spriteWidth,
height = currentChara.spriteHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = charaMultiplier,
obscure = obscure
)
val nameBitmapData = BitmapData(
bitmap = currentChara.nameSprite,
width = currentChara.nameSpriteWidth,
height = currentChara.nameSpriteHeight
)
val nameImageBitmapData = nameBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = nameMultiplier,
obscure = obscure
)
Dialog(
onDismissRequest = onClickClose
) {
Card (
modifier = Modifier
.fillMaxWidth()
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
Row (
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
) {
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(R.string.dex_chara_icon_description),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
colorFilter = when (obscure) {
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
false -> null
},
filterQuality = FilterQuality.None
)
}
Spacer(
modifier = Modifier
.padding(16.dp)
)
if (!obscure) {
Column {
Image(
bitmap = nameImageBitmapData.imageBitmap,
contentDescription = stringResource(R.string.dex_chara_name_icon_description),
modifier = Modifier
.width(nameImageBitmapData.dpWidth)
.height(nameImageBitmapData.dpHeight),
filterQuality = FilterQuality.None
)
Spacer(modifier = Modifier.padding(4.dp))
if (currentChara.baseHp != 65535) {
Text(
text = stringResource(
R.string.dex_chara_stats,
currentChara.baseHp,
currentChara.baseBp,
currentChara.baseAp
)
)
Text(
text = stringResource(
R.string.dex_chara_stage_attribute,
romanNumeralsStage,
currentChara.attribute.toString().substring(0, 2)
)
)
}
}
} else {
Column {
Text(stringResource(R.string.dex_chara_unknown_name))
Spacer(modifier = Modifier.padding(4.dp))
Text(stringResource(R.string.dex_chara_stage_attribute_unknown))
Text(stringResource(R.string.dex_chara_stats_unknown))
}
}
}
Spacer(modifier = Modifier.padding(16.dp))
Column {
currentCharaPossibleTransformations.map {
val selectedCharaBitmap = BitmapData(
bitmap = it.spriteIdle,
width = it.spriteWidth,
height = it.spriteHeight
)
val selectedCharaImageBitmap = selectedCharaBitmap.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = it.discoveredOn == null
)
Card (
modifier = Modifier
.padding(vertical = 8.dp)
) {
Row (
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
) {
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = selectedCharaImageBitmap.imageBitmap,
contentDescription = stringResource(R.string.dex_chara_icon_description),
modifier = Modifier
.size(selectedCharaImageBitmap.dpWidth)
.padding(8.dp),
colorFilter = when (it.discoveredOn == null) {
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
false -> null
},
filterQuality = FilterQuality.None
)
}
Spacer(
modifier = Modifier
.padding(16.dp)
)
Column {
Text(
text = stringResource(
R.string.dex_chara_requirements,
it.requiredTrophies,
it.requiredBattles,
it.requiredVitals,
it.requiredWinRate,
it.changeTimerHours
)
)
Text(
text = stringResource(
R.string.dex_chara_adventure_level,
it.requiredAdventureLevelCompleted + 1
)
)
}
}
}
}
}
Row {
if (currentCharaPossibleFusions.isNotEmpty()) {
Button(
onClick = {
showFusions = true
}
) {
Text(stringResource(R.string.dex_chara_fusions_button))
}
}
Spacer(
modifier = Modifier
.padding(4.dp)
)
Button(
onClick = onClickClose
) {
Text(stringResource(R.string.dex_chara_close_button))
}
}
}
}
}
if (showFusions) {
DexCharaFusionsDialog(
currentChara = currentChara,
currentCharaPossibleFusions = currentCharaPossibleFusions,
onClickDismiss = {
showFusions = false
},
obscure = obscure
)
}
}
@@ -0,0 +1,191 @@
package com.github.nacabaro.vbhelper.screens.cardScreen.dialogs
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
@Composable
fun DexCharaFusionsDialog(
currentChara: CharacterDtos.CardCharaProgress,
currentCharaPossibleFusions: List<CharacterDtos.FusionsWithSpritesAndObtained>,
obscure: Boolean,
onClickDismiss: () -> Unit,
) {
val nameMultiplier = 3
val charaMultiplier = 4
val charaBitmapData = BitmapData(
bitmap = currentChara.spriteIdle,
width = currentChara.spriteWidth,
height = currentChara.spriteHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = charaMultiplier,
obscure = obscure
)
val nameBitmapData = BitmapData(
bitmap = currentChara.nameSprite,
width = currentChara.nameSpriteWidth,
height = currentChara.nameSpriteHeight
)
val nameImageBitmapData = nameBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = nameMultiplier,
obscure = obscure
)
Dialog(
onDismissRequest = onClickDismiss,
) {
Card(
modifier = Modifier
.fillMaxWidth()
) {
Column(
modifier = Modifier
.padding(16.dp)
) {
Row {
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
colorFilter = when (obscure) {
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
false -> null
},
filterQuality = FilterQuality.None
)
}
Spacer(
modifier = Modifier
.padding(16.dp)
)
if (!obscure) {
Column {
Image(
bitmap = nameImageBitmapData.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.width(nameImageBitmapData.dpWidth)
.height(nameImageBitmapData.dpHeight),
filterQuality = FilterQuality.None
)
}
} else {
Column {
Text(text = "????????????????")
}
}
}
Spacer(modifier = Modifier.padding(16.dp))
Column {
currentCharaPossibleFusions.map {
val selectedCharaBitmap = BitmapData(
bitmap = it.spriteIdle,
width = it.spriteWidth,
height = it.spriteHeight
)
val selectedCharaImageBitmap = selectedCharaBitmap.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = it.discoveredOn == null
)
Card (
modifier = Modifier
.padding(vertical = 8.dp)
) {
Row (
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
) {
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = selectedCharaImageBitmap.imageBitmap,
contentDescription = "Icon",
modifier = Modifier
.size(selectedCharaImageBitmap.dpWidth)
.padding(8.dp),
colorFilter = when (it.discoveredOn == null) {
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
false -> null
},
filterQuality = FilterQuality.None
)
}
Spacer(
modifier = Modifier
.padding(16.dp)
)
Column {
Text("Combine with ${it.fusionAttribute}")
}
}
}
}
}
Button(
onClick = onClickDismiss
) {
Text("Close")
}
}
}
}
}
@@ -0,0 +1,48 @@
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.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
@Composable
fun BetaWarning(
onDismissRequest: () -> Unit
) {
Dialog(
onDismissRequest = onDismissRequest
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = stringResource(R.string.beta_warning_message_main)
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = stringResource(R.string.beta_warning_message_compatibility)
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = stringResource(R.string.beta_warning_message_thanks)
)
Spacer(modifier = Modifier.padding(8.dp))
Button(
onClick = onDismissRequest
) {
Text(text = stringResource(R.string.beta_warning_button_dismiss))
}
}
}
}
}
@@ -1,56 +1,121 @@
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.collectAsState
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.res.stringResource
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.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
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.source.CardRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.flowOf
import kotlin.collections.emptyList
@Composable
fun HomeScreen(
navController: NavController
navController: NavController,
homeScreenController: HomeScreenControllerImpl
) {
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val activeMon = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
val beData = remember { mutableStateOf<BECharacterData?>(null) }
val vbData = remember { mutableStateOf<VBCharacterData?>(null) }
val cardRepository = CardRepository(application.container.db)
LaunchedEffect(storageRepository, activeMon) {
withContext(Dispatchers.IO) {
activeMon.value = storageRepository.getActiveCharacter()
if (activeMon.value != null) {
beData.value = storageRepository.getCharacterBeData(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
val activeMon by storageRepository
.getActiveCharacter()
.collectAsState(initial = null)
val cardIconData by (
activeMon
?.let { chara ->
cardRepository.getCardIconByCharaId(chara.charId)
}
?: flowOf<CardDtos.CardIcon?>(null)
).collectAsState(initial = null)
val transformationHistory by (
activeMon
?.let { chara ->
storageRepository.getTransformationHistory(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbSpecialMissions by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getSpecialMissions(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbData by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getCharacterVbData(chara.id)
}
?: flowOf<VBCharacterData?>(null)
).collectAsState(initial = null)
val beData by (
activeMon
?.takeIf { it.characterType == DeviceType.BEDevice }
?.let { chara ->
storageRepository.getCharacterBeData(chara.id)
}
?: flowOf<BECharacterData?>(null)
).collectAsState(initial = null)
var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }
var betaWarning by rememberSaveable { mutableStateOf(true) }
var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) }
var collectedCurrency by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(true) {
homeScreenController
.didAdventureMissionsFinish {
adventureMissionsFinished = it
}
}
Scaffold (
topBar = {
TopBanner(
text = "VB Helper",
text = stringResource(R.string.home_title),
onScanClick = {
navController.navigate(NavigationItems.Scan.route)
},
@@ -60,7 +125,7 @@ fun HomeScreen(
)
}
) { contentPadding ->
if (activeMon.value == null || (beData.value == null && vbData.value == null) || transformationHistory.value == null) {
if (activeMon == null || (beData == null && vbData == null) || cardIconData == null || transformationHistory.isEmpty()) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
@@ -68,35 +133,93 @@ fun HomeScreen(
.fillMaxSize()
.padding(top = contentPadding.calculateTopPadding())
) {
Text(text = "Nothing to see here")
Text(text = stringResource(R.string.adventure_empty_state))
}
} else {
if (activeMon.value!!.isBemCard) {
Log.d("HomeScreen", "BEDeviceBEm")
val cardIcon = BitmapData(
bitmap = cardIconData!!.cardIcon,
width = cardIconData!!.cardIconWidth,
height = cardIconData!!.cardIconHeight
)
if (activeMon!!.isBemCard && beData != null) {
BEBEmHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
activeMon = activeMon!!,
beData = beData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
cardIcon = cardIcon
)
} else if (!activeMon.value!!.isBemCard && activeMon.value!!.characterType == DeviceType.BEDevice) {
Log.d("HomeScreen", "BEDevice")
} else if (!activeMon!!.isBemCard && activeMon!!.characterType == DeviceType.BEDevice && beData != null) {
BEDiMHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
activeMon = activeMon!!,
beData = beData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
cardIcon = cardIcon
)
} else {
} else if (vbData != null) {
VBDiMHomeScreen(
activeMon = activeMon.value!!,
vbData = vbData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
activeMon = activeMon!!,
vbData = vbData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
specialMissions = vbSpecialMissions,
homeScreenController = homeScreenController,
onClickCollect = { item, currency ->
collectedItem = item
collectedCurrency = currency
},
cardIcon = cardIcon
)
}
}
}
if (collectedItem != null) {
ObtainedItemDialog(
obtainedItem = collectedItem!!,
obtainedCurrency = collectedCurrency!!,
onClickDismiss = {
collectedItem = null
collectedCurrency = null
}
)
}
if (adventureMissionsFinished) {
Dialog(
onDismissRequest = { adventureMissionsFinished = false },
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = stringResource(R.string.home_adventure_mission_finished),
textAlign = TextAlign.Center
)
Button(
onClick = {
adventureMissionsFinished = false
},
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text(text = stringResource(R.string.beta_warning_button_dismiss))
}
}
}
}
}
if (betaWarning) {
BetaWarning {
betaWarning = false
}
}
}
@@ -0,0 +1,9 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface HomeScreenController {
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit)
}
@@ -0,0 +1,79 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.time.Instant
import kotlin.math.roundToInt
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()
.first()
val finishedAdventureCharacters = adventureCharacters.filter { character ->
character.finishesAdventure <= currentTime
}
onCompletion(finishedAdventureCharacters.isNotEmpty())
}
}
override fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit) {
componentActivity.lifecycleScope.launch {
database
.specialMissionDao()
.clearSpecialMission(missionId)
if (missionCompletion == SpecialMission.Status.COMPLETED) {
val randomItem = database
.itemDao()
.getAllItems()
.first()
.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
)
val randomAmount = (2..6).random() * 1000
val currentCurrency = application.container.currencyRepository.currencyValue.first()
application.container.currencyRepository.setCurrencyValue(currentCurrency + randomAmount)
onCleared(purchasedItem, randomAmount)
} else {
onCleared(null, null)
}
}
}
}
@@ -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
@@ -26,6 +26,7 @@ import java.util.Locale
fun BEBEmHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
beData: BECharacterData,
cardIcon: BitmapData,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues
) {
@@ -46,6 +47,7 @@ fun BEBEmHomeScreen(
),
multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small,
cardIcon = cardIcon,
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
@@ -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
@@ -26,6 +26,7 @@ import kotlin.text.format
@Composable
fun BEDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
beData: BECharacterData,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues
@@ -45,6 +46,7 @@ fun BEDiMHomeScreen(
width = activeMon.spriteWidth,
height = activeMon.spriteHeight
),
cardIcon = cardIcon,
multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier
@@ -0,0 +1,193 @@
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
import androidx.compose.ui.res.stringResource
@Composable
fun VBDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
vbData: VBCharacterData,
specialMissions: List<SpecialMissions>,
homeScreenController: HomeScreenControllerImpl,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues,
onClickCollect: (ItemDtos.PurchasedItem?, Int?) -> 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
),
cardIcon = cardIcon,
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 = stringResource(R.string.home_vbdim_vitals),
modifier = Modifier
.weight(0.5f)
.aspectRatio(1f)
.padding(8.dp)
)
ItemDisplay(
icon = R.drawable.baseline_trophy_24,
textValue = activeMon.trophies.toString(),
definition = stringResource(R.string.home_vbdim_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 = stringResource(R.string.home_vbdim_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 = stringResource(R.string.home_vbdim_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 = stringResource(R.string.home_vbdim_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 = stringResource(R.string.home_vbdim_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 = stringResource(R.string.home_vbdim_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, mission.status, onClickCollect)
}
}
}
}
}
@@ -15,15 +15,19 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
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.flow.first
import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable
fun ChooseCharacterScreen(
@@ -33,16 +37,31 @@ fun ChooseCharacterScreen(
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val storageRepository = StorageRepository(application.container.db, )
val characterList = remember {
mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList())
}
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.getBECharacters()
}
ItemType.VBITEM -> {
characterList.value = storageRepository.getVBCharacters()
}
ItemType.SPECIALMISSION-> {
characterList.value = storageRepository.getVBCharacters()
}
else -> {
characterList.value = storageRepository.getAllCharacters().first()
}
}
}
}
@@ -51,7 +70,7 @@ fun ChooseCharacterScreen(
itemsScreenController.applyItem(itemId, selectedCharacter!!) {
Toast.makeText(
application.applicationContext,
"Item applied!",
application.getString(R.string.choose_character_item_applied),
Toast.LENGTH_SHORT
).show()
navController.popBackStack()
@@ -62,7 +81,7 @@ fun ChooseCharacterScreen(
Scaffold(
topBar = {
TopBanner(
text = "Choose character",
text = stringResource(R.string.choose_character_title),
onBackClick = {
navController.popBackStack()
}
@@ -1,12 +1,10 @@
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
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -19,59 +17,25 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.domain.items.ItemType
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@Composable
fun ItemElement(
itemIcon: Int,
lengthIcon: Int,
modifier: Modifier = Modifier,
onClick: (() -> Unit) = { }
) {
Card (
onClick = onClick,
modifier = modifier
.aspectRatio(1f)
) {
Box(modifier = Modifier.fillMaxSize()) {
// Background image (full size)
Icon(
painter = painterResource(id = itemIcon),
contentDescription = null,
modifier = Modifier
.size(96.dp)
.align(Alignment.Center)
)
Icon(
painter = painterResource(id = lengthIcon),
contentDescription = null,
tint = MaterialTheme.colorScheme.surfaceTint,
modifier = Modifier
.size(48.dp) // Set the size of the overlay image
.padding(4.dp
)
.align(Alignment.TopStart) // Align to the top end (top-right corner)
)
}
}
}
@Composable
fun ItemDialog(
name: String,
description: String,
itemIcon: Int,
lengthIcon: Int,
amount: Int,
onClickUse: () -> Unit,
onClickCancel: () -> Unit
item: ItemDtos.ItemsWithQuantities,
onClickCancel: () -> Unit,
onClickUse: (() -> Unit)? = null,
onClickPurchase: (() -> Unit)? = null,
) {
Dialog(
onDismissRequest = onClickCancel,
@@ -93,14 +57,14 @@ fun ItemDialog(
Box(modifier = Modifier) {
// Background image (full size)
Icon(
painter = painterResource(id = itemIcon),
painter = painterResource(id = getIconResource(item.itemIcon)),
contentDescription = null,
modifier = Modifier
.size(96.dp)
.align(Alignment.Center)
)
Icon(
painter = painterResource(id = lengthIcon),
painter = painterResource(id = getLengthResource(item.itemLength)),
contentDescription = null,
tint = MaterialTheme.colorScheme.outline,
modifier = Modifier
@@ -114,7 +78,7 @@ fun ItemDialog(
) {
Text(
fontSize = MaterialTheme.typography.titleLarge.fontSize,
text = name,
text = item.name,
modifier = Modifier
.fillMaxWidth()
)
@@ -124,32 +88,62 @@ fun ItemDialog(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
text = description
text = item.description,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
)
if (onClickPurchase != null) {
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have $amount of this item",
text = stringResource(
R.string.item_dialog_costs_credits,
item.price
),
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
}
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.item_dialog_you_have_quantity,
item.quantity
),
modifier = Modifier
.fillMaxWidth()
)
Row (
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
) {
if (onClickUse != null) {
Button(
onClick = onClickUse
) {
Text("Use item")
Text(stringResource(R.string.item_dialog_use))
}
}
if (onClickPurchase != null) {
Button(
onClick = onClickPurchase
) {
Text(stringResource(R.string.item_dialog_purchase))
}
}
Spacer(modifier = Modifier.size(8.dp))
Button(
onClick = onClickCancel
) {
Text("Cancel")
Text(stringResource(R.string.item_dialog_cancel))
}
}
}
@@ -157,55 +151,23 @@ fun ItemDialog(
}
}
fun getIconResource(index: Int): Int {
return when (index) {
ItemsScreenControllerImpl.ItemTypes.PPTraining.id -> R.drawable.baseline_agility_24
ItemsScreenControllerImpl.ItemTypes.APTraining.id -> R.drawable.baseline_attack_24
ItemsScreenControllerImpl.ItemTypes.HPTraining.id -> R.drawable.baseline_shield_24
ItemsScreenControllerImpl.ItemTypes.BPTraining.id -> R.drawable.baseline_trophy_24
ItemsScreenControllerImpl.ItemTypes.AllTraining.id -> R.drawable.baseline_arrow_up_24
6 -> R.drawable.baseline_timer_24
7 -> R.drawable.baseline_rank_24
8 -> R.drawable.baseline_vitals_24
else -> R.drawable.baseline_question_mark_24
}
}
fun getLengthResource(index: Int): Int {
return when (index) {
15 -> R.drawable.baseline_15_min_timer
30 -> R.drawable.baseline_30_min_timer
60 -> R.drawable.baseline_60_min_timer
-60 -> R.drawable.baseline_60_min_timer
300 -> R.drawable.baseline_5_hour_timer
600 -> R.drawable.baseline_10_hour_timer
-720 -> R.drawable.baseline_12_hour_timer
-1440 -> R.drawable.baseline_24_hour_timer
6000 -> R.drawable.baseline_reset_24
1000 -> R.drawable.baseline_single_arrow_up
2500 -> R.drawable.baseline_double_arrow_up
5000 -> R.drawable.baseline_triple_arrow_up
9999 -> R.drawable.baseline_health_24
-500 -> R.drawable.baseline_single_arrow_down
-1000 -> R.drawable.baseline_double_arrow_down
-2500 -> R.drawable.baseline_triple_arrow_down
-9999 -> R.drawable.baseline_reset_24
else -> R.drawable.baseline_question_mark_24
}
}
@Composable
@Preview(showBackground = true)
fun PreviewItemDialog() {
VBHelperTheme {
ItemDialog(
item = ItemDtos.ItemsWithQuantities(
name = "AP Training x3 (60 min)",
description = "Boosts AP during training (for 60 minutes)",
itemIcon = R.drawable.baseline_attack_24,
lengthIcon = R.drawable.baseline_60_min_timer,
itemLength = R.drawable.baseline_60_min_timer,
quantity = 19,
id = 1,
price = 500,
itemType = ItemType.BEITEM
),
onClickUse = { },
onClickCancel = { },
amount = 19
onClickCancel = { }
)
}
}
@@ -0,0 +1,50 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
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.Icon
import androidx.compose.material3.MaterialTheme
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.unit.dp
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.R
@Composable
fun ItemElement(
item: ItemDtos.ItemsWithQuantities,
modifier: Modifier = Modifier,
onClick: (() -> Unit) = { }
) {
Card (
onClick = onClick,
modifier = modifier
.aspectRatio(1f)
) {
Box(modifier = Modifier.fillMaxSize()) {
Icon(
painter = painterResource(id = getIconResource(item.itemIcon)),
contentDescription = null,
modifier = Modifier
.size(96.dp)
.align(Alignment.Center)
.padding(16.dp)
)
Icon(
painter = painterResource(id = getLengthResource(item.itemLength)),
contentDescription = null,
tint = MaterialTheme.colorScheme.surfaceTint,
modifier = Modifier
.size(48.dp)
.align(Alignment.TopStart)
.padding(8.dp)
)
}
}
}
@@ -0,0 +1,40 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import com.github.nacabaro.vbhelper.R
fun getIconResource(index: Int): Int {
return when (index) {
ItemsScreenControllerImpl.ItemTypes.PPTraining.id -> R.drawable.baseline_agility_24
ItemsScreenControllerImpl.ItemTypes.APTraining.id -> R.drawable.baseline_attack_24
ItemsScreenControllerImpl.ItemTypes.HPTraining.id -> R.drawable.baseline_shield_24
ItemsScreenControllerImpl.ItemTypes.BPTraining.id -> R.drawable.baseline_trophy_24
ItemsScreenControllerImpl.ItemTypes.AllTraining.id -> R.drawable.baseline_arrow_up_24
6 -> R.drawable.baseline_timer_24
7 -> R.drawable.baseline_rank_24
8 -> R.drawable.baseline_vitals_24
else -> R.drawable.baseline_question_mark_24
}
}
fun getLengthResource(index: Int): Int {
return when (index) {
15 -> R.drawable.baseline_15_min_timer
30 -> R.drawable.baseline_30_min_timer
60 -> R.drawable.baseline_60_min_timer
-60 -> R.drawable.baseline_60_min_timer
300 -> R.drawable.baseline_5_hour_timer
600 -> R.drawable.baseline_10_hour_timer
-720 -> R.drawable.baseline_12_hour_timer
-1440 -> R.drawable.baseline_24_hour_timer
6000 -> R.drawable.baseline_reset_24
1000 -> R.drawable.baseline_single_arrow_up
2500 -> R.drawable.baseline_double_arrow_up
5000 -> R.drawable.baseline_triple_arrow_up
9999 -> R.drawable.baseline_health_24
-500 -> R.drawable.baseline_single_arrow_down
-1000 -> R.drawable.baseline_double_arrow_down
-2500 -> R.drawable.baseline_triple_arrow_down
-9999 -> R.drawable.baseline_reset_24
else -> R.drawable.baseline_question_mark_24
}
}
@@ -17,6 +17,9 @@ import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun ItemsScreen(
@@ -30,14 +33,14 @@ fun ItemsScreen(
Scaffold(
topBar = {
Column {
TopBanner("Items")
TopBanner(text = stringResource(R.string.items_title))
TabRow(
selectedTabIndex = selectedTabItem,
modifier = Modifier
) {
items.forEachIndexed { index, item ->
Tab(
text = { Text(item.label) },
text = { Text(text = stringResource(item.label)) },
selected = selectedTabItem == index,
onClick = { selectedTabItem = index }
)
@@ -2,12 +2,17 @@ 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
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -24,7 +29,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 +50,27 @@ 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)
beCharacterData = database
.userCharacterDao()
.getBeData(characterId)
.firstOrNull()
} else if (characterData.characterType == DeviceType.VBDevice) {
vbCharacterData = database
.userCharacterDao()
.getVbData(characterId)
.firstOrNull()
}
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 +95,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 +120,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 +137,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
}
private fun consumeItem(itemId: Long) {
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.first().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 suspend fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities {
return database
.itemDao()
.getItem(itemId)
}
private suspend fun consumeItem(itemId: Long) {
database
.itemDao()
.useItem(itemId)
@@ -1,29 +1,37 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.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.res.stringResource
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.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable
fun ItemsStore(
@@ -31,45 +39,102 @@ fun ItemsStore(
) {
val application = LocalContext.current.applicationContext as VBHelper
val itemsRepository = ItemsRepository(application.container.db)
val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) }
val myItems by itemsRepository.getAllItems().collectAsState(emptyList())
var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(itemsRepository) {
withContext(Dispatchers.IO) {
myItems.value = itemsRepository.getAllItems()
val currencyRepository = application.container.currencyRepository
val currentCurrency = currencyRepository.currencyValue.collectAsState(0)
val scope = rememberCoroutineScope()
if (myItems.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text(stringResource(R.string.items_no_items))
}
} else {
Column() {
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text(
text = stringResource(
R.string.items_store_credits,
currentCurrency.value
),
modifier = Modifier
.padding(8.dp)
)
}
if (myItems.value.isEmpty()) {
Text("No items")
} else {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
) {
items(myItems.value) { index ->
items(myItems) { index ->
ItemElement(
itemIcon = getIconResource(index.itemIcon),
lengthIcon = getLengthResource(index.itemLength),
item = index,
modifier = Modifier
.padding(8.dp),
onClick = {
selectedElementIndex = myItems.value.indexOf(index)
selectedElementIndex = myItems.indexOf(index)
}
)
}
}
}
}
if (selectedElementIndex != null) {
ItemDialog(
name = myItems.value[selectedElementIndex!!].name,
description = myItems.value[selectedElementIndex!!].description,
itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon),
lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength),
amount = myItems.value[selectedElementIndex!!].quantity,
onClickUse = { },
item = myItems[selectedElementIndex!!],
onClickPurchase = {
scope.launch {
Toast.makeText(
application.applicationContext,
application.getString(
purchaseItem(
application.container.db,
myItems[selectedElementIndex!!],
currencyRepository
)
),
Toast.LENGTH_SHORT
).show()
}
},
onClickCancel = { selectedElementIndex = null }
)
}
}
suspend fun purchaseItem(
db: AppDatabase,
item: ItemDtos.ItemsWithQuantities,
currencyRepository: CurrencyRepository
): Int {
return if (currencyRepository.currencyValue.first() < item.price) {
R.string.items_not_enough_credits
} else {
db
.itemDao()
.purchaseItem(
item.id,
1
)
currencyRepository
.setCurrencyValue(
currencyRepository.currencyValue.first() - item.price
)
R.string.items_purchase_success
}
}
@@ -9,7 +9,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -17,18 +17,14 @@ 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.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.ItemDialog
import com.github.nacabaro.vbhelper.components.ItemElement
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.components.getLengthResource
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import com.github.nacabaro.vbhelper.R
@Composable
fun MyItems(
@@ -36,36 +32,30 @@ fun MyItems(
) {
val application = LocalContext.current.applicationContext as VBHelper
val itemsRepository = ItemsRepository(application.container.db)
val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) }
val myItems by itemsRepository.getUserItems().collectAsState(emptyList())
var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(itemsRepository) {
withContext(Dispatchers.IO) {
myItems.value = itemsRepository.getUserItems()
}
}
if (myItems.value.isEmpty()) {
if (myItems.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text("No items")
Text(stringResource(R.string.items_no_items))
}
} else {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
) {
items(myItems.value) { index ->
items(myItems) { index ->
ItemElement(
itemIcon = getIconResource(index.itemIcon),
lengthIcon = getLengthResource(index.itemLength),
item = index,
modifier = Modifier
.padding(8.dp),
onClick = {
selectedElementIndex = myItems.value.indexOf(index)
selectedElementIndex = myItems.indexOf(index)
}
)
}
@@ -73,11 +63,7 @@ fun MyItems(
if (selectedElementIndex != null) {
ItemDialog(
name = myItems.value[selectedElementIndex!!].name,
description = myItems.value[selectedElementIndex!!].description,
itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon),
lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength),
amount = myItems.value[selectedElementIndex!!].quantity,
item = myItems[selectedElementIndex!!],
onClickUse = {
navController
.navigate(
@@ -85,7 +71,7 @@ fun MyItems(
.ApplyItem.route
.replace(
"{itemId}",
myItems.value[selectedElementIndex!!].id.toString()
myItems[selectedElementIndex!!].id.toString()
)
)
selectedElementIndex = null
@@ -0,0 +1,118 @@
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
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun ObtainedItemDialog(
obtainedItem: ItemDtos.PurchasedItem,
obtainedCurrency: Int,
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 = stringResource(
R.string.obtained_item_you_have,
obtainedItem.itemAmount
),
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp)
)
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.obtained_item_you_also_got_credits,
obtainedCurrency
),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp)
)
Button(
onClick = onClickDismiss,
modifier = Modifier
.fillMaxWidth()
) {
Text(text = stringResource(R.string.obtained_item_dismiss))
}
}
}
}
}
}
@@ -0,0 +1,80 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
navController: NavController
) {
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.scan_title),
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
ScanButton(
text = stringResource(R.string.scan_vb_to_app),
disabled = onClickRead == null,
onClick = onClickRead?: { },
)
Spacer(modifier = Modifier.height(16.dp))
ScanButton(
text = stringResource(R.string.scan_app_to_vb),
disabled = onClickWrite == null,
onClick = onClickWrite?: { },
)
}
}
}
@Composable
fun ScanButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
disabled: Boolean = false,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = !disabled,
) {
Text(
text = text,
fontSize = 16.sp,
modifier = Modifier
.padding(4.dp)
)
}
}
@@ -1,43 +1,31 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.ReadingScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.WritingScreen
import com.github.nacabaro.vbhelper.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
import com.github.nacabaro.vbhelper.R
const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER"
@@ -46,125 +34,69 @@ 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 writingScreen by remember { mutableStateOf(false) }
var readingScreen by remember { mutableStateOf(false) }
override fun onResume() {
scanScreenController.onClickRead(secrets!!) {
isDoneReadingCharacter = true
}
}
})
scanScreenController.onClickRead(secrets!!) {
isDoneReadingCharacter = true
}
} else if (writingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
if (writingScreen && nfcCharacter != null && characterId != null) {
WritingScreen(
scanScreenController = scanScreenController,
nfcCharacter = nfcCharacter!!,
characterId = characterId,
onComplete = {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
},
onCancel = {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
}
)
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
onDispose {
if(readingScreen || writingScreen) {
scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter) {
} else if (readingScreen) {
ReadingScreen(
scanScreenController = scanScreenController,
onCancel = {
readingScreen = false
navController.navigate(NavigationItems.Home.route)
} else if (isDoneSendingCard && isDoneWritingCharacter) {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
LaunchedEffect(storageRepository) {
withContext(Dispatchers.IO) {
storageRepository
.deleteCharacter(characterId!!)
}
}
}
if (readingScreen) {
ReadingCharacterScreen("Reading character") {
},
onComplete = {
readingScreen = false
scanScreenController.cancelRead()
}
} else if (writingScreen) {
if (!isDoneSendingCard) {
ReadingCharacterScreen("Sending card") {
writingScreen = false
scanScreenController.cancelRead()
}
} else if (!isDoneWritingCharacter) {
ReadingCharacterScreen("Writing character") {
isDoneSendingCard = false
writingScreen = false
scanScreenController.cancelRead()
}
navController.navigate(NavigationItems.Home.route)
}
)
} else {
ChooseConnectOption(
onClickRead = when {
characterId != null -> null
!launchedFromHomeScreen -> null
else -> {
{
if(secrets == null) {
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.scan_secrets_not_initialized), Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.scan_secrets_not_imported), Toast.LENGTH_SHORT).show()
} else {
readingScreen = true // kicks off nfc adapter in DisposableEffect
}
@@ -176,71 +108,22 @@ fun ScanScreen(
else -> {
{
if(secrets == null) {
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.scan_secrets_not_initialized), Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.scan_secrets_not_imported), Toast.LENGTH_SHORT).show()
} else {
writingScreen = true // kicks off nfc adapter in DisposableEffect
}
}
}
}
},
navController = navController
)
}
}
@Composable
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
) {
Scaffold(
topBar = { TopBanner(text = "Scan a Vital Bracelet") }
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
ScanButton(
text = "Vital Bracelet to App",
disabled = onClickRead == null,
onClick = onClickRead?: { },
)
Spacer(modifier = Modifier.height(16.dp))
ScanButton(
text = "App to Vital Bracelet",
disabled = onClickWrite == null,
onClick = onClickWrite?: { },
)
}
}
}
@Composable
fun ScanButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
disabled: Boolean = false,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = !disabled,
) {
Text(
text = text,
fontSize = 16.sp,
modifier = Modifier
.padding(4.dp)
)
}
}
@Preview(showBackground = true)
@Composable
fun ScanScreenPreview() {
@@ -255,11 +138,15 @@ fun ScanScreenPreview() {
) {
}
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {}
override fun flushCharacter(cardId: Long) {}
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit, onMultipleCards: (List<Card>) -> Unit) {}
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, onMultipleCards: (List<Card>, NfcCharacter) -> Unit): String { return "" }
override suspend fun characterToNfc(characterId: Long): NfcCharacter? { return null }
},
characterId = null
characterId = null,
launchedFromHomeScreen = false
)
}
@@ -2,12 +2,13 @@ package com.github.nacabaro.vbhelper.screens.scanScreen
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.flow.Flow
interface ScanScreenController {
val secretsFlow: Flow<Secrets>
fun onClickRead(secrets: Secrets, onComplete: ()->Unit)
fun onClickRead(secrets: Secrets, onComplete: ()->Unit, onMultipleCards: (List<Card>) -> Unit)
fun onClickCheckCard(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit)
fun onClickWrite(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit)
@@ -15,4 +16,12 @@ interface ScanScreenController {
fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener)
fun unregisterActivityLifecycleListener(key: String)
fun flushCharacter(cardId: Long)
fun characterFromNfc(
nfcCharacter: NfcCharacter,
onMultipleCards: (List<Card>, NfcCharacter) -> Unit
): String
suspend fun characterToNfc(characterId: Long): NfcCharacter?
}
@@ -11,8 +11,13 @@ import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vbnfc.TagCommunicator
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.domain.card.Card
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
@@ -20,30 +25,34 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
class ScanScreenControllerImpl(
override val secretsFlow: Flow<Secrets>,
private val nfcHandler: (NfcCharacter)->String,
private val context: ComponentActivity,
private val componentActivity: ComponentActivity,
private val registerActivityLifecycleListener: (String, ActivityLifecycleListener)->Unit,
private val unregisterActivityLifecycleListener: (String)->Unit,
): ScanScreenController {
private var lastScannedCharacter: NfcCharacter? = null
private val nfcAdapter: NfcAdapter
init {
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(context)
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity)
if (maybeNfcAdapter == null) {
Toast.makeText(context, "No NFC on device!", Toast.LENGTH_SHORT).show()
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_no_nfc_on_device), Toast.LENGTH_SHORT).show()
}
nfcAdapter = maybeNfcAdapter
checkSecrets()
}
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit, onMultipleCards: (List<Card>) -> Unit) {
handleTag(secrets) { tagCommunicator ->
val character = tagCommunicator.receiveCharacter()
val resultMessage = nfcHandler(character)
val resultMessage = characterFromNfc(character) { cards, nfcCharacter ->
lastScannedCharacter = nfcCharacter
onMultipleCards(cards)
}
onComplete.invoke()
resultMessage
}
@@ -51,7 +60,7 @@ class ScanScreenControllerImpl(
override fun cancelRead() {
if(nfcAdapter.isEnabled) {
nfcAdapter.disableReaderMode(context)
nfcAdapter.disableReaderMode(componentActivity)
}
}
@@ -74,7 +83,7 @@ class ScanScreenControllerImpl(
val options = Bundle()
// Work around for some broken Nfc firmware implementations that poll the card too fast
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250)
nfcAdapter.enableReaderMode(context, buildOnReadTag(secrets, handlerFunc), NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
nfcAdapter.enableReaderMode(componentActivity, buildOnReadTag(secrets, handlerFunc), NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
options
)
}
@@ -85,26 +94,26 @@ class ScanScreenControllerImpl(
return { tag->
val nfcData = NfcA.get(tag)
if (nfcData == null) {
context.runOnUiThread {
Toast.makeText(context, "Tag detected is not VB", Toast.LENGTH_SHORT).show()
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_tag_not_vb), Toast.LENGTH_SHORT).show()
}
}
nfcData.connect()
nfcData.use {
val tagCommunicator = TagCommunicator.getInstance(nfcData, secrets.getCryptographicTransformerMap())
val successText = handlerFunc(tagCommunicator)
context.runOnUiThread {
Toast.makeText(context, successText, Toast.LENGTH_SHORT).show()
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, successText, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun checkSecrets() {
context.lifecycleScope.launch(Dispatchers.IO) {
if(secretsFlow.stateIn(context.lifecycleScope).value.isMissingSecrets()) {
context.runOnUiThread {
Toast.makeText(context, "Missing Secrets. Go to settings and import Vital Arena APK", Toast.LENGTH_SHORT).show()
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
if(secretsFlow.stateIn(componentActivity.lifecycleScope).value.isMissingSecrets()) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_missing_secrets), Toast.LENGTH_SHORT).show()
}
}
}
@@ -117,12 +126,20 @@ 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!"
componentActivity.getString(R.string.scan_sent_character_success)
} catch (e: Throwable) {
Log.e("TAG", e.stackTraceToString())
"Whoops"
componentActivity.getString(R.string.scan_error_generic)
}
}
}
@@ -135,13 +152,46 @@ class ScanScreenControllerImpl(
handleTag(secrets) { tagCommunicator ->
tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId)
onComplete.invoke()
"Sent DIM successfully!"
componentActivity.getString(R.string.scan_sent_dim_success)
}
}
// EXTRACTED DIRECTLY FROM EXAMPLE APP
private fun showWirelessSettings() {
Toast.makeText(context, "NFC must be enabled", Toast.LENGTH_SHORT).show()
context.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_nfc_must_be_enabled), Toast.LENGTH_SHORT).show()
componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
}
override fun characterFromNfc(
nfcCharacter: NfcCharacter,
onMultipleCards: (List<Card>, NfcCharacter) -> Unit
): String {
val nfcConverter = FromNfcConverter(
componentActivity = componentActivity
)
return nfcConverter.addCharacter(nfcCharacter, onMultipleCards)
}
override suspend fun characterToNfc(characterId: Long): NfcCharacter {
val nfcGenerator = ToNfcConverter(
componentActivity = componentActivity
)
val character = nfcGenerator.characterToNfc(characterId)
Log.d("CharacterType", character.toString())
return character
}
override fun flushCharacter(cardId: Long) {
val nfcConverter = FromNfcConverter(
componentActivity = componentActivity
)
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
if (lastScannedCharacter != null) {
nfcConverter.addCharacterUsingCard(lastScannedCharacter!!, cardId)
lastScannedCharacter = null
}
}
}
}
@@ -0,0 +1,48 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
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.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.scanScreen.cardSelect.ScanCardEntry
import com.github.nacabaro.vbhelper.utils.BitmapData
@Composable
fun ChooseCard(
cards: List<Card>,
onCardSelected: (Card) -> Unit
) {
Scaffold (
topBar = {
TopBanner(
text = "Choose card",
)
}
) { contentPadding ->
LazyColumn (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(cards) {
ScanCardEntry(
name = it.name,
logo = BitmapData(
it.logo,
it.logoWidth,
it.logoHeight
),
onClick = {
onCardSelected(it)
},
modifier = Modifier
.padding(8.dp)
)
}
}
}
}
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.components
package com.github.nacabaro.vbhelper.screens.scanScreen.cardSelect
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
@@ -21,11 +21,9 @@ import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
@Composable
fun DexDiMEntry(
fun ScanCardEntry(
name: String,
logo: BitmapData,
obtainedCharacters: Int,
totalCharacters: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
@@ -56,17 +54,12 @@ fun DexDiMEntry(
Column(
modifier = Modifier
.padding(8.dp)
.weight(1f)
) {
Text(
text = name,
modifier = Modifier
)
Text(
text = "$obtainedCharacters of $totalCharacters characters obtained",
fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
fontSize = MaterialTheme.typography.labelSmall.fontSize,
modifier = Modifier
)
}
}
}
@@ -0,0 +1,303 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters
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.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 addCharacterUsingCard(
nfcCharacter: NfcCharacter,
cardId: Long
): String {
val cardData = database
.cardDao()
.getCardById(cardId)
if (cardData == null) {
return "Card not found"
}
return insertCharacter(nfcCharacter, cardData)
}
fun addCharacter(
nfcCharacter: NfcCharacter,
onMultipleCards: (List<Card>, NfcCharacter) -> Unit
): String {
val appReservedCardId = nfcCharacter
.appReserved2[0].toLong()
var cardData: Card? = null
if (appReservedCardId != 0L) {
val fetchedCard = database
.cardDao()
.getCardById(appReservedCardId)
if (fetchedCard == null) {
return "Card not found"
} else if (fetchedCard.cardId == nfcCharacter.dimId.toInt()) {
cardData = fetchedCard
}
}
if (cardData == null) {
val allCards = database
.cardDao()
.getCardByCardId(nfcCharacter.dimId.toInt())
if (allCards.isEmpty())
return "Card not found"
if (allCards.size > 1) {
onMultipleCards(allCards, nfcCharacter)
return "Multiple cards found"
}
cardData = allCards[0]
}
return insertCharacter(nfcCharacter, cardData)
}
private fun insertCharacter(
nfcCharacter: NfcCharacter,
cardData: Card
): String {
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
) {
database
.cardProgressDao()
.updateCardProgress(
currentStage = nfcCharacter.nextAdventureMissionStage.toInt(),
cardId = cardData.id,
unlocked = nfcCharacter.nextAdventureMissionStage.toInt() > cardData.stageCount,
)
}
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 ->
val year = if (historyElement.year.toInt() !in 2021 .. 2035) 0 else historyElement.year.toInt()
VitalsHistory(
charId = characterId,
year = year,
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,298 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.icu.util.Calendar
import android.icu.util.TimeZone
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 kotlinx.coroutines.flow.first
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)
return if (userCharacter.characterType == DeviceType.BEDevice)
nfcToBENfc(characterId, characterInfo, userCharacter)
else
nfcToVBNfc(characterId, characterInfo, userCharacter)
}
private suspend fun nfcToVBNfc(
characterId: Long,
characterInfo: CharacterDtos.CardCharacterInfo,
userCharacter: UserCharacter
): VBNfcCharacter {
val vbData = database
.userCharacterDao()
.getVbData(characterId)
.first()
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 = characterInfo.currentStage.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 = generateUShortAppReserved(userCharacter),
generation = vbData.generation.toUShort(),
totalTrophies = vbData.totalTrophies.toUShort(),
specialMissions = watchSpecialMissions.toTypedArray()
)
return nfcData
}
private suspend fun generateUShortAppReserved(
userCharacter: UserCharacter
): Array<UShort> {
val cardData = database
.cardDao()
.getCardByCharacterId(userCharacter.id)
.first()
val appReserved = Array<UShort>(3) {
0u
}
appReserved[0] = cardData.id.toUShort()
return appReserved
}
private suspend fun generateSpecialMissionsArray(
characterId: Long
): List<SpecialMission> {
val specialMissions = database
.userCharacterDao()
.getSpecialMissions(characterId)
.first()
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,
userCharacter: UserCharacter
): BENfcCharacter {
val beData = database
.userCharacterDao()
.getBeData(characterId)
.first()
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 = characterInfo.currentStage.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)
.first()
.map {
val date = Date(it.transformationDate)
val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC"))
calendar.time = date
Log.d(
"TransformationHistory",
"Year: ${calendar.get(Calendar.YEAR)}, " +
"Month: ${calendar.get(Calendar.MONTH) + 1}, " +
"Day: ${calendar.get(Calendar.DAY_OF_MONTH)}"
)
NfcCharacter.Transformation(
toCharIndex = it.monIndex.toUByte(),
year = calendar
.get(Calendar.YEAR)
.toUShort(),
month = (calendar
.get(Calendar.MONTH) + 1)
.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
}
}
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -10,17 +10,22 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadingCharacterScreen(
fun ActionScreen(
topBannerText: String,
onClickCancel: () -> Unit,
) {
Scaffold (
topBar = {
TopBanner(topBannerText)
TopBanner(
text = topBannerText,
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column (
@@ -30,12 +35,12 @@ fun ReadingCharacterScreen(
.padding(innerPadding)
.fillMaxSize()
) {
Text("Place your Vital Bracelet near the reader...")
Text(stringResource(R.string.action_place_near_reader))
Button(
onClick = onClickCancel,
modifier = Modifier.padding(16.dp)
) {
Text("Cancel")
Text(stringResource(R.string.action_cancel))
}
}
}
@@ -0,0 +1,61 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadCharacterScreen(
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.read_character_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Text(
text = stringResource(R.string.read_character_prepare_device),
textAlign = TextAlign.Center
)
Text(
text = stringResource(R.string.read_character_go_to_connect),
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.padding(8.dp)
)
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.read_character_confirm))
}
}
}
}
@@ -0,0 +1,112 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadingScreen(
scanScreenController: ScanScreenController,
onCancel: () -> Unit,
onComplete: () -> Unit
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
var readingScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var cardSelectScreen by remember { mutableStateOf(false) }
DisposableEffect(readingScreen) {
if(readingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object: ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
}
)
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false
onComplete()
}
if (!readingScreen) {
ReadCharacterScreen(
onClickConfirm = {
readingScreen = true
},
onClickCancel = {
onCancel()
}
)
}
if (readingScreen) {
ActionScreen(topBannerText = stringResource(R.string.reading_character_title),) {
readingScreen = false
scanScreenController.cancelRead()
onCancel()
}
} else if (cardSelectScreen) {
ChooseCard(
cards = cardsRead!!,
onCardSelected = { card ->
cardSelectScreen = false
scanScreenController.flushCharacter(card.id)
}
)
}
}
@@ -0,0 +1,142 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable
fun WriteCardScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.write_card_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(
R.string.write_card_icon_description
),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text(stringResource(R.string.write_card_device_ready))
Text(
stringResource(
R.string.write_card_required_card,
cardDetails.name
)
)
}
}
}
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.write_card_confirm))
}
}
}
}
@@ -0,0 +1,138 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable
fun WriteCharacterScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.write_character_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(R.string.write_character_icon_description),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text(stringResource(R.string.write_character_success))
Text(stringResource(R.string.write_character_wait_ready))
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.write_character_confirm))
}
}
}
}
@@ -0,0 +1,142 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun WritingScreen(
scanScreenController: ScanScreenController,
characterId: Long,
nfcCharacter: NfcCharacter,
onComplete: () -> Unit,
onCancel: () -> Unit,
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
var writing by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var writingConfirmScreen by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
DisposableEffect(writing) {
if (writing) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
)
if (secrets != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
onDispose {
if (writing) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (!writingScreen) {
writing = false
WriteCardScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingScreen = true
}
)
} else if (!isDoneSendingCard) {
writing = true
ActionScreen( topBannerText = stringResource(R.string.sending_card_title)) {
scanScreenController.cancelRead()
onCancel()
}
} else if (!writingConfirmScreen) {
writing = false
WriteCharacterScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingConfirmScreen = true
}
)
} else if (!isDoneWritingCharacter) {
writing = true
ActionScreen(topBannerText = stringResource(R.string.writing_character_action_title)) {
isDoneSendingCard = false
scanScreenController.cancelRead()
onCancel()
}
}
var completedWriting by remember { mutableStateOf(false) }
LaunchedEffect(isDoneSendingCard, isDoneWritingCharacter) {
withContext(Dispatchers.IO) {
if (isDoneSendingCard && isDoneWritingCharacter) {
storageRepository
.deleteCharacter(characterId)
completedWriting = true
}
}
}
if (completedWriting) {
onComplete()
}
}

Some files were not shown because too many files have changed in this diff Show More